Agent Based Models in Mata

Maarten Buis University of Konstanz
maarten.buis@uni.kn -------------------------------------------------------------------------------

index >>

-------------------------------------------------------------------------------

Table of Contents
    Introduction

        What is an Agent Based Model?

        Mata

        The abm_grid class
 
    Building Agent Based Models

        the Schellings segregation model

        Other models
 
    Conclusion
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Introduction -- What is an Agent Based Model?
-------------------------------------------------------------------------------

What is an Agent Based Model?
An Agent Based Model is a simulation in which agents, that each follow simple rules, interact with one another and thus produce a often surprising outcome at the macro level.
The purpose of an ABM is to explore mechanisms through which actions of the individual agents add up to a macro outcome, by varying the rules that agents have to follow or varying with whom the agent can interact.
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Introduction -- What is an Agent Based Model?
-------------------------------------------------------------------------------

Example 1: Immunization rate
Below is an example Agent Based Model that illustrates how a sufficiently high immunization rate protects even the non-immunized individuals in a population, and also allows one to explore at what immunization rate this protection disappears.
In this model a single agent lives in each cell of a square grid and does not move. An agent could have 5 states. She could be:
immunized not-immunized and vulnerable not-immunized and infected not-immunized and infectious not-immunized, healed, and no longer vulnerable
In the first round a single random vulnerable agent becomes infected.
In all subsequent rounds
all infected agents become infectious, and infect all vulnerable agents within radius cell radius. All infectious agents become healed and no longer vulnerable.

. set seed 123456789
. set rmsg on r; t=0.00 16:52:06
. clear mata r; t=0.00 16:52:06
. drop _all r; t=0.00 16:52:06
. run infection.mata r; t=0.01 16:52:06
. . // run a single model and graph the evolution of the infection over time . mata ------------------------------------------------- mata (type end to exit) ----- : foo = immune()
: foo.rdim(40)
: foo.cdim(40)
: foo.tdim(24)
: foo.radius(2)
: foo.not_vaccinated(240)
: foo.run()
: foo.extract(("id", "time", "status", "y", "x"))
: end ------------------------------------------------------------------------------- r; t=0.15 16:52:06
. . replace y = - y (6,000 real changes made) r; t=0.00 16:52:06
. . mata st_local("rdim", strofreal(foo.rdim())) r; t=0.00 16:52:06
. mata st_local("cdim", strofreal(foo.cdim())) r; t=0.00 16:52:06
. mata st_local("tdim", strofreal(foo.tdim())) r; t=0.00 16:52:06
. tempfile data r; t=0.00 16:52:06
. save `data' file C:\Users\Admin\AppData\Local\Temp\ST_2310_000008.tmp saved r; t=0.00 16:52:06
. drop _all r; t=0.00 16:52:06
. set obs `rdim' number of observations (_N) was 0, now 40 r; t=0.00 16:52:06
. gen x = _n r; t=0.00 16:52:06
. expand `cdim' (1,560 observations created) r; t=0.00 16:52:06
. bys x : gen y = -_n r; t=0.00 16:52:06
. expand `tdim' + 1 (38,400 observations created) r; t=0.00 16:52:06
. bys y x : gen time = _n r; t=0.06 16:52:07
. merge 1:1 x y time using `data'
Result # of obs. ----------------------------------------- not matched 34,000 from master 34,000 (_merge==1) from using 0 (_merge==2)
matched 6,000 (_merge==3) ----------------------------------------- r; t=0.08 16:52:07
. assert _merge != 2 r; t=0.00 16:52:07
. drop _merge r; t=0.00 16:52:07
. . forvalues t = 1/25 { 2. scatter y x if status == 0 & time == `t' , /// > msymbol(Oh) || /// > scatter y x if status == 1 & time == `t' , /// > msymbol(S) mcolor(gs8) || /// > scatter y x if status == 2 & time == `t' , /// > msymbol(S) || /// > scatter y x if status == 3 & time == `t' , /// > msymbol(+) || /// > scatter y x if status == . & time == `t', /// > msymbol(o) msize(*.10) mcolor(gs8) /// > aspect(1) name(gr`t', replace) /// > yscale(off range(-41.5 -0.5)) /// > xscale(off range(.5 41.5)) /// > plotregion(margin(zero)) /// > title("Iteration `t'") /// > legend(order( 1 "vulnerable" /// > 2 "infected" /// > 3 "infectious" /// > 4 "healed/immunized" /// > 5 "immunized")) 3. } r; t=16.13 16:52:23
. . // run multiple models and plot the seriousness of the infection against . // the immunization rate . mata ------------------------------------------------- mata (type end to exit) ----- : scen = (16, 80, 120, 160, 200, 240, 280, 320, 360, 400)
: res = J(200,2,.)
: k=0
: for (i = 1; i <= 10 ; i++) { > printf("number of vulnerable: %3.0f \n", scen[i]) > foo.not_vaccinated(scen[i]) > for(j = 1; j<= 20; j++) { > k = k +1 > foo.run() > res[k,.] = scen[i], foo.n_infected() > } > } number of vulnerable: 16 number of vulnerable: 80 number of vulnerable: 120 number of vulnerable: 160 number of vulnerable: 200 number of vulnerable: 240 number of vulnerable: 280 number of vulnerable: 320 number of vulnerable: 360 number of vulnerable: 400
: : idx = st_addvar("float", ("N","n"))
: st_addobs(200)
: st_store((1..200)',idx,res)
: end ------------------------------------------------------------------------------- r; t=17.44 16:52:40
. . gen perc_notvac = N/(40*40)*100 (40,000 missing values generated) r; t=0.00 16:52:40
. gen perc_inf = n/N*100 (40,000 missing values generated) r; t=0.00 16:52:40
. . scatter perc_inf perc_notvac, msymbol(o) mcolor(%20) /// > xtitle("percentage not vaccinated") /// > ytitle("percentage infected of not vacinated ") /// > name(scenarios, replace) r; t=0.36 16:52:41
. .
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Introduction -- What is an Agent Based Model?
-------------------------------------------------------------------------------

Example 2: Schellings segregation model
In this model there are blue and red agents, that live in cells on a square grid. Each cell can contain only one agent, but not all cells are occupied. An agent is happy if the proportion of agents of the same colour in her neighbourhood is above a certain cut-off value. If she is unhappy she will move to the nearest available cell that does satisfy her needs.
The point of this simulation that even with a low cut-off value, like 30%, you will get fairly seggregated neighbourhoods.


. run schelling.mata
. . mata ------------------------------------------------- mata (type end to exit) ----- : foo = schelling()
: foo.rdim(10)
: foo.cdim(10)
: foo.tdim(5)
: foo.nred(45)
: foo.nblue(45)
: foo.limit(.3)
: foo.run()
: foo.summtable()
Average proportion of neighbours with same color and average proportion of happy agents
----------------------------------- Iteration prop same prop happy ----------------------------------- 0 .556 .822 1 .758 .956 2 .799 .989 3 .808 .989 4 .82 .989 5 .829 1 -----------------------------------
: foo.extract(("r","c","t", "id", "red", "happy"))
: end -------------------------------------------------------------------------------
. . replace r = -r (540 real changes made)
. . forvalues t = 0/5 { 2. twoway scatter r c if red == 0 & t == `t' & happy == 1, /// > msymbol(O) mcolor(blue) msize(*2) /// > mlabel(id) mlabpos(0) || /// > scatter r c if red == 1 & t == `t' & happy == 1, /// > msymbol(O) mcolor(red) msize(*2) /// > mlabel(id) mlabpos(0) || /// > scatter r c if red == 0 & t == `t' & happy == 0, /// > msymbol(Oh) mcolor(blue) msize(*2) /// > mlabel(id) mlabpos(0) || /// > scatter r c if red == 1 & t == `t' & happy == 0, /// > title(iteration `i') /// > msymbol(Oh) mcolor(red) msize(*2) /// > mlabel(id) mlabpos(0) /// > yscale(off range(-10.5 -.5)) /// > xscale(off range(.5 10.5)) /// > ylab(none) xlab(none) /// > plotregion(margin(zero)) /// > aspect(1) /// > xline(.5(1)9.5) /// > yline(-.5(-1)-9.5) /// > legend(off) /// > name(grsch`t', replace) title(Iteration `t') 3. }
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Introduction -- What is an Agent Based Model?
-------------------------------------------------------------------------------

Example 3: Bots influencing the mood
In this simulation there are agents that have a mood ranging from 10 (good) to 1 (bad).
In each iteration an agent reads a single newsitems. If the newsitem is good it increases the mood, and if it is bad it decreases the mood.
The chance of choosing an item is proportional to the number of agents that have previously read that article.
An item disappears after a certain number of iterations and a new newsitem is created with a random number of initial "clicks".
A "bot" tries to decrease the mood by adding additional clicks to the initial clicks of bad newsitems.

. run abm_clicks.mata
. . mata ------------------------------------------------- mata (type end to exit) ----- : foo = clicks()
: foo.tdim(200)
: foo.pr_good(.5)
: foo.botclicks(50)
: foo.run()
: foo.mood(("m1","m2","m3","m4","m5","m6","m7","m8","m9","m10"))
: end -------------------------------------------------------------------------------
. gen t = _n-1
. sort t
. forvalues i = 2/10 { 2. local j = `i' - 1 3. replace m`i' = m`i' + m`j' 4. } (201 real changes made) (201 real changes made) (201 real changes made) (201 real changes made) (201 real changes made) (201 real changes made) (201 real changes made) (201 real changes made) (201 real changes made)
. . twoway area m10 t, color("165 0 38") || /// > area m9 t, color("215 48 39") || /// > area m8 t, color("244 109 67") || /// > area m7 t, color("253 174 97") || /// > area m6 t, color("254 224 144") || /// > area m5 t, color("224 243 248") || /// > area m4 t, color("171 217 233") || /// > area m3 t, color("116 173 209") || /// > area m2 t, color(" 69 117 180") || /// > area m1 t, color(" 49 54 149") /// > plotregion(margin(zero)) /// > name(mood, replace) /// > legend(order(1 "10 good" /// > 2 "9" /// > 3 "8" /// > 4 "7" /// > 5 "6" /// > 6 "5" /// > 7 "4" /// > 8 "3" /// > 9 "2" /// > 10 "1 bad" ))
. drop _all
. mata foo.items(("id", "t", "what", "val"))
. reshape wide val, i(id t) j(what) (note: j = 1 2)
Data long -> wide ----------------------------------------------------------------------------- Number of obs. 8040 -> 4020 Number of variables 4 -> 4 j variable (2 values) what -> (dropped) xij variables: val -> val1 val2 -----------------------------------------------------------------------------
. . gen clicks_good = val2*val1
. gen clicks_bad = val2*(1-val1)
. collapse (sum) clicks_good clicks_bad, by(t)
. twoway line clicks_good clicks_bad t, /// > lcolor("165 0 38" " 49 54 149") /// > lpattern(solid solid) /// > legend(order(1 "good" 2 "bad")) /// > ytitle(total number of clicks) /// > name(clicks, replace)
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Introduction -- What is an Agent Based Model?
-------------------------------------------------------------------------------

What is this talk about?
This talk reports on an experiment to find out if it is viable to do ABMs in Stata.
In particular many ABMs have tasks in common, e.g. in many cases agents "live" inside a square grid and we want to know who their "neighbours" are. How can we create and maintain a library of these common functions. This library needs to be:
convenient to "import" in your model
modular
easy to maintain and extend
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Introduction -- What is an Agent Based Model?
-------------------------------------------------------------------------------

The alternatives
There are various dedicated environments for creating ABMs.
netlogo (netlogo) http://ccl.northwestern.edu/netlogo/
repast (java, python, C++, and others) https://repast.github.io/
mason (java) https://cs.gmu.edu/~eclab/projects/mason/
mesa (python) https://mesa.readthedocs.io/
People who primarily create ABMs are not going to move away from those, and rightly so.
So the toolkits discussed in this presentation should be aimed at people who primarily use Stata and occationally want to make an ABM.
The question becomes: can we make making ABMs in Stata easier/quicker/more convenient for this target audience than learning one of these environments.
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Introduction -- Mata
-------------------------------------------------------------------------------

Entering and exiting Mata, scalars, matrices
For implementing ABMs we will be using Stata's matrix language Mata
Here I give only a very brief introduction, for much more see:
William W. Gould (2018) The Mata Book: A Book for Serious Programmers and Those Who Want to Be, College Station, TX: Stata Press.

You enter Mata by typing mata in Stata, and you leave it by typing end
In it you can work with matrices

. mata ------------------------------------------------- mata (type end to exit) ----- : foo = 3
: foo 3
: : foo = 1, 2 \ 3, 4
: foo 1 2 +---------+ 1 | 1 2 | 2 | 3 4 | +---------+
: : bar = J(2,2,1)
: bar [symmetric] 1 2 +---------+ 1 | 1 | 2 | 1 1 | +---------+
: foo + bar 1 2 +---------+ 1 | 2 3 | 2 | 4 5 | +---------+
: end -------------------------------------------------------------------------------
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Introduction -- Mata
-------------------------------------------------------------------------------

Associative Array
However, matrices aren't the only thing that Mata can work with.
An ABM consist of agents that have properties, maybe a grid on which agents live, etc.
A lot of the work in ABMs is just storing those properties, reading them, and changing them.
We could do that in matrices, but associative arrays tend to be more flexible
In an associative array you store key and value pairs. In the example below the key is the last name, while the value is the first name. The value could be anything.

. mata ------------------------------------------------- mata (type end to exit) ----- : name = AssociativeArray()
: name.put("Cox", "Nicholas")
: name.put("Jann", "Ben")
: name.put("Buis", "Maarten")
: : name.get("Jann") Ben
: end -------------------------------------------------------------------------------
    The key can also be a vector, leading to a 2, 3, or higher dimensional
    array.


. mata ------------------------------------------------- mata (type end to exit) ----- : name.reinit("string",2)
: name.put(("Cox","middle"),"J.")
: name.put(("Cox", "first"), "Nicholas")
: name.put(("Jann", "first"), "Ben")
: name.put(("Buis", "middle"), "Leendert")
: name.put(("Buis", "first"), "Maarten")
: : name.get(("Cox", "middle")) J.
: end -------------------------------------------------------------------------------
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Introduction -- Mata
-------------------------------------------------------------------------------

Writing functions
When we write a new function we need to tell Mata what "kind of thing" it can expect that function to return, and the kind of things the function can expect as inputs
Things (scalars, matrices) we create inside a function are local to that function
It is good practice to also first declare what kind of thing it is before creating it.

. clear mata
. . mata: ------------------------------------------------- mata (type end to exit) ----- : : real scalar sum2(real scalar a, real scalar b) > { > real scalar result > > result = a + b > > return(result) > }
: : sum2(2,4) 6
: end -------------------------------------------------------------------------------
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Introduction -- Mata
-------------------------------------------------------------------------------

Loops, if
Looping and conditional execution are basic actions happing sometime in any program
To illustrate the syntax used in Mata I add all even numbers between 1 and 10

. mata ------------------------------------------------- mata (type end to exit) ----- : result = 0
: for(i=1 ; i<=10; i++) { > if (mod(i,2)==0) { > result = result + i > } > }
: result 30
: end -------------------------------------------------------------------------------
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Introduction -- Mata
-------------------------------------------------------------------------------

structure
If you want to implement a larger project, like an ABM, then you will want to split it up in sub-routines rather than put it all in one big program.
However, you will often have quite a lot of "raw material" that you want to share between sub-routines
In that case it is convenient to use a structure to organize that raw material on a "tray", and pass that tray to the sub-routines.

. clear mata
. . mata: ------------------------------------------------- mata (type end to exit) ----- : // ------------- define what goes where on our "tray" : struct datastruct { > real scalar a, b > }
: : // ------------------------------------ main function : void arithmatic(real scalar a, real scalar b) > { > // create the tray called data > struct datastruct scalar data > > // fill that tray > data.a = a > data.b = b > > // pass that tray on to sub-routines > sum2(data) > prod2(data) > }
: : // --------------------------------- the sub-routines : real scalar sum2(struct datastruct scalar data) > { > return(data.a + data.b) > }
: : real scalar prod2(struct datastruct scalar data) > { > return(data.a * data.b) > }
: : // ---------------------------- use the main function : arithmatic(2,4) 6 8
: end -------------------------------------------------------------------------------
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Introduction -- Mata
-------------------------------------------------------------------------------

Class
The next step is to use a class, which you can think of as a structure that can also contain functions
The functions are local to the class
Each function has access to all material stored in the class
An "outsider", the user, can only interact with the public functions, protected and private functions and variables can only be accessed from within the class.

. clear mata
. . mata: ------------------------------------------------- mata (type end to exit) ----- : : class arithmatic { > protected: > real scalar a > real scalar b > real scalar sum() > real scalar prod() > > public: > void input() > void run() > }
: : void arithmatic::input(real scalar aval, real scalar bval) > { > a = aval > b = bval > }
: : real scalar arithmatic::sum() > { > return(a + b) > }
: : real scalar arithmatic::prod() > { > return(a * b) > }
: : void arithmatic::run() > { > sum() > prod() > }
: : // how the user interacts with this program : math = arithmatic()
: math.input(2,4)
: math.run() 6 8
: end -------------------------------------------------------------------------------
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Introduction -- Mata
-------------------------------------------------------------------------------

Inheritance
Classes can inherrit from other classes: All variables and functions in the parent class also exist in the child class
The child class can add new functions
Function in the child class with the same name hide the function from the parent.
That is a model for distributing common functions in ABMs

. mata ------------------------------------------------- mata (type end to exit) ----- : class arith extends arithmatic > { > private: > real scalar ratio() > > public: > void run() > }
: : real scalar arith::ratio() > { > return(a/b) > }
: : void arith::run() { > sum() > prod() > ratio() > }
: : math = arith()
: math.input(2, 4)
: math.run() 6 8 .5
: end -------------------------------------------------------------------------------
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Introduction -- The abm_grid class
-------------------------------------------------------------------------------

Setting up a grid and finding neighbours
This class helps create and use a grid
The abm_grid class is available from GitHub: https://github.com/maartenteaches/abm_grid
The main file is abm_grid.mata, all other files there are help files.
abm_grid
Below is an example that uses abm_grid to create a 10 by 10 grid that is constant over time and allows for 1 agent per cell

. clear mata
. run abm_grid.mata
. . mata ------------------------------------------------- mata (type end to exit) ----- : grid = abm_grid()
: grid.rdim(10)
: grid.cdim(10)
: grid.setup()
: end -------------------------------------------------------------------------------
    We use a grid in ABMs because it defines who the neighbours of an agent
    are.


. mata ------------------------------------------------- mata (type end to exit) ----- : grid.find_ring(3,3,1) 1 2 +---------+ 1 | 4 4 | 2 | 3 4 | 3 | 2 4 | 4 | 2 3 | 5 | 2 2 | 6 | 3 2 | 7 | 4 2 | 8 | 4 3 | +---------+
: grid.find_ring(3,1,1) 1 2 +---------+ 1 | 4 2 | 2 | 3 2 | 3 | 2 2 | 4 | 2 1 | 5 | 4 1 | +---------+
: end -------------------------------------------------------------------------------

| 1 2 3 4 | 1 2 3 4 --+------------ --+------------ 1 | 1 | 2 | 5 4 3 2 | 4 3 3 | 6 o 2 3 | o 2 4 | 7 8 1 4 | 5 1

By default the cells that are outside the grid are ignored, but that can be changed to let the grid "wrap around".

. mata ------------------------------------------------- mata (type end to exit) ----- : grid.torus(1)
: grid.setup()
: : grid.find_ring(3,1,1) 1 2 +-----------+ 1 | 4 2 | 2 | 3 2 | 3 | 2 2 | 4 | 2 1 | 5 | 2 10 | 6 | 3 10 | 7 | 4 10 | 8 | 4 1 | +-----------+
: end -------------------------------------------------------------------------------

| 1 2 3 4 .. 10 --+----------------- 1 | 2 | 4 3 5 3 | o 2 6 4 | 8 1 7

By default a neighbourhood with radius 1 is defined as all cells 1 step removed, where a step could be horizontal, vertical, or diagonal. We could change that to allow only horizontal and vertical steps.

. mata ------------------------------------------------- mata (type end to exit) ----- : grid.neumann(1)
: grid.setup()
: : grid.find_ring(3,3,1) 1 2 +---------+ 1 | 4 3 | 2 | 3 4 | 3 | 2 3 | 4 | 3 2 | +---------+
: end -------------------------------------------------------------------------------

| 1 2 3 4 --+------------ 1 | 2 | 3 3 | 4 o 2 4 | 1
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Introduction -- The abm_grid class
-------------------------------------------------------------------------------

Managing agents
In the grid we only store the agent id, the properties of those agents (e.g. her color for Schelling seggregation model) should be stored in a seperate associative array
The abm_grid class has function to check if an agent exists on a give cell, to create an agent on a cell, and to retrieve the agent id of the agent living at a cell.

. mata ------------------------------------------------- mata (type end to exit) ----- : grid.has_agent(1,1) 0
: grid.create_agent(1,1,5)
: grid.has_agent(1,1) 1
: grid.agent_id(1,1) 5
: end -------------------------------------------------------------------------------
    The abm_grid class also has functions to move, copy, or delete agents


. mata ------------------------------------------------- mata (type end to exit) ----- : //move agents : grid.move_agent(1,1,2,2)
: grid.has_agent(1,1) 0
: grid.has_agent(2,2) 1
: grid.agent_id(2,2) 5
: : //copy agents : grid.copy_agent(2,2,1,1)
: grid.agent_id(2,2) 5
: grid.agent_id(1,1) 5
: : // delete agents : grid.delete_agent(2,2)
: grid.has_agent(2,2) 0
: end -------------------------------------------------------------------------------
    After the ABM has run we often want to export the results to Stata to
    create graphs



. mata ------------------------------------------------- mata (type end to exit) ----- : grid.create_agent(5,3,2)
: grid.create_agent(2,9,1)
: grid.extract() 1 2 3 4 5 +---------------------+ 1 | 1 1 0 1 5 | 2 | 2 9 0 1 1 | 3 | 5 3 0 1 2 | +---------------------+
: end -------------------------------------------------------------------------------
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Introduction -- The abm_grid class
-------------------------------------------------------------------------------

Adding a time dimension to the grid
We need to add a time dimension to our grid if agents can move around on the grid, e.g. in Schelling's segregation model.

. mata ------------------------------------------------- mata (type end to exit) ----- : grid.tdim(5)
: grid.setup()
: : // we setup the intial grid : grid.create_agent(1,1,5, 0)
: grid.create_agent(3,4,1, 0)
: grid.create_agent(4,4,2, 0)
: : // next iteration we first copy the grid : grid.copy_grid(0,1)
: // and make changes as necessary : grid.move_agent(1,1,2,3,1)
: : //at the end we can recover the entire history of the grid: : grid.extract() 1 2 3 4 5 +---------------------+ 1 | 1 1 0 1 5 | 2 | 2 3 1 1 5 | 3 | 3 4 0 1 1 | 4 | 3 4 1 1 1 | 5 | 4 4 0 1 2 | 6 | 4 4 1 1 2 | +---------------------+
: end -------------------------------------------------------------------------------
    If the agents cannot move, e.g. in the immunization model, then grid does
    not need a time dimension.

Now the associative array storing the agents needs to have a time dimension.
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Introduction -- The abm_grid class
-------------------------------------------------------------------------------

Multiple agents per cell
For some ABMs we want multiple agents to live in the same cell
For example, a predator-prey model where sheep and wolve agents wander on a grid
Each agent still needs a distinct "adress", so we can add a "location" dimension within cells

. mata ------------------------------------------------- mata (type end to exit) ----- : grid.idim(3)
: grid.setup()
: : // add three agents to the cell 1,1 at itereation 0 : grid.create_agent(1,1,5, 0,1)
: grid.create_agent(1,1,6, 0,2)
: grid.create_agent(1,1,7, 0,3)
: : grid.agent_id(1,1,0,1) 5
: grid.agent_ids(1,1,0) 1 2 3 +-------------+ 1 | 5 6 7 | +-------------+
: end -------------------------------------------------------------------------------
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Introduction -- The abm_grid class
-------------------------------------------------------------------------------

How do I use it?
A typical start of an ABM would be:

. clear mata
. run abm_grid.mata
. . mata ------------------------------------------------- mata (type end to exit) ----- : class grid extends abm_grid > { > > }
: : class abm > { > private: > class grid scalar universe > > // other things required for the model > }
: end -------------------------------------------------------------------------------
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Building Agent Based Models -- the Schellings segregation model
-------------------------------------------------------------------------------

How to start
I start with a rough idea of what the model should be, e.g.:
In this model there are blue and red agents, that live in cells on a square grid. Each cell can contain only one agent, but not all cells are occupied. An agent is happy if the proportion of agents of the same colour in her neighbourhood is above a certain cut-off value. If she is unhappy she will move to the nearest available cell that does satisfy her needs.
From that I start defining the class, what kind of variables do I need to store, what kind of functions do I need.
This class definition becomes the todo list for the project.
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        void             rdim()
        void             cdim()
        void             tdim()
        void             limit()
        void             nblue()
        void             nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}
end
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Building Agent Based Models -- the Schellings segregation model
-------------------------------------------------------------------------------

Receiving and returning settings
We want the user to be able to specify
the number of rows and columns in the grid the number of iterations the number of red and blue agents the limit, below which the agent becomes unhappy
Lets start with the number of rows:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        void             rdim()
        void             cdim()
        void             tdim()
        void             limit()
        void             nblue()
        void             nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// ------------------------------------------------------------------ setup grid
void schelling::rdim(real scalar dim) 
{
    string scalar errmsg
    
    if (dim <= 0 | mod(dim,1)!= 0 ){
        errmsg = "dim must be a positive integer"
        _error(3300, errmsg)
    }

    universe.rdim(dim)
    rdim = dim
}
end

We are checking the input to see if it is a positive integer, and we will have to do that for most of the other settings as well. Lets put that in a separate function:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        void             rdim()
        void             cdim()
        void             tdim()
        void             limit()
        void             nblue()
        void             nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
void schelling::rdim(real scalar dim) 
{
    is_posint(dim, "dim")    
    universe.rdim(dim)
    rdim = dim
}
end

Can't we make it such that if we give rdim() an argument it sets rdim to that value, but we don't give rdim() an argument it returns the value of rdim?
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}

schel = schelling()
schel.rdim(5)
schel.rdim()
end

That looks good, just extend to all other settings
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prob()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
end
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Building Agent Based Models -- the Schellings segregation model
-------------------------------------------------------------------------------

Setting up
Seting up the universe is something we can let abm_grid do, the only thing we need to add is to make sure tdim is specified:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prob()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
end

To setup the agents we first need to check some of the options:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prob()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
}
end

Now we need to change the agents associative array such that the key is the agent id (a real number) and fill it with red and blue agents:

clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prob()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}
end

The final step is put these agents on random places on the grid in the initial iteration:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prob()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

end
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Building Agent Based Models -- the Schellings segregation model
-------------------------------------------------------------------------------

Summarize the neighbourhood
Now we agents living on the grid. The next step would be for them to look around and see if they are happy with their neighbourhood.
Lets start with a routine that returns the color of an agent living at a certain cell:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prob()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// ------------------------------------------------------------ helper functions

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

end

Next we loop over all neighbours and count the proportion of same color neighbours:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prob()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// ------------------------------------------------------------ helper functions

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c) 
{
    real   scalar    k, s, i, j, l
    real   matrix    neigh
    string scalar self
    
    self = get_color(t,r,c)
    
    neigh = universe.find_ring(r,c,1)
    k = 0 // number of neighbours
    s = 0 // number of same color neighbours
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        if (universe.has_agent(i,j,t) ) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return(s/k)
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

end

Right now a missing value will be returned if an agent has no neighbours, we need to fix that :
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prob()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// ------------------------------------------------------------ helper functions

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c) 
{
    real   scalar    k, s, i, j, l
    real   matrix    neigh
    string scalar self
    
    self = get_color(t,r,c)
    
    neigh = universe.find_ring(r,c,1)
    k = 0 // number of neighbours
    s = 0 // number of same color neighbours
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        if (universe.has_agent(i,j,t) ) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

end

We are later also going to use this function to find new locations. In that case the focal cell is not the cell where the agent currently lives:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prob()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// ------------------------------------------------------------ helper functions

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0 // number of neighbours
    s = 0 // number of same color neighbours
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        if (universe.has_agent(i,j,t) ) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

end

In that case the neighbourhood of the focal cell could contain the cell where the agent currently lives. We would want to exclude that cell, as she will be moving:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prob()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// ------------------------------------------------------------ helper functions

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l, isself
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0 // number of neighbours
    s = 0 // number of same color neighbours
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        isself = (i==rs & j==cs)
        if (universe.has_agent(i,j,t) & !isself) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

end

Now we can create another function that runs sum_neighbourhood() and compares the returned proportion with the limit:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prob()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// ------------------------------------------------------------ helper functions

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l, isself
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0 // number of neighbours
    s = 0 // number of same color neighbours
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        isself = (i==rs & j==cs)
        if (universe.has_agent(i,j,t) & !isself) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

real scalar schelling::is_happy(
    real scalar t , real scalar r , real scalar c , 
    real scalar ts, real scalar rs, real scalar cs)
{
    real   scalar prop
        
    prop   = sum_neighbours(t,r,c,ts,rs,cs)
    
    return(prop >= limit)
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

end
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Building Agent Based Models -- the Schellings segregation model
-------------------------------------------------------------------------------

Move to a better cell
There is an function in abm_grid that can move an agent from one location to another, so we only need to focus on finding the nearest location that will make the agent happy.
Lets start with a loop that moves over all cells in the grid in an outward spiral:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prob()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// ------------------------------------------------------------ helper functions


void schelling::move(real scalar t, real scalar r, real scalar c)
{
    real scalar    radius, i, maxradius
    real matrix    neigh
    
    maxradius = max( ( rdim-r, r-1, cdim-c, c-1 ) )

    for (radius = 1 ; radius <= maxradius ; radius ++) {
        neigh = universe.find_ring(r,c,radius)
        for (i = 1; i <= rows(neigh); i++) {
        
        }
    }
}

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l, isself
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0 // number of neighbours
    s = 0 // number of same color neighbours
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        isself = (i==rs & j==cs)
        if (universe.has_agent(i,j,t) & !isself) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

real scalar schelling::is_happy(
    real scalar t , real scalar r , real scalar c , 
    real scalar ts, real scalar rs, real scalar cs)
{
    real   scalar prop
        
    prop   = sum_neighbours(t,r,c,ts,rs,cs)
    
    return(prop >= limit)
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

end

Now we can check any empty cells to see if the agent will be happy there, and move when that is the case:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prob()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// ------------------------------------------------------------ helper functions


void schelling::move(real scalar t, real scalar r, real scalar c)
{
    real scalar    radius, i, maxradius, rd, cd
    real matrix    neigh
    
    maxradius = max( ( rdim-r, r-1, cdim-c, c-1 ) )

    for (radius = 1 ; radius <= maxradius ; radius ++) {
        neigh = universe.find_ring(r,c,radius)
        for (i = 1; i <= rows(neigh); i++) {
            rd = neigh[i,1]
            cd = neigh[i,2]
            if (universe.has_agent(rd,cd, t+1) == 0) {
                if ( is_happy(t+1, rd, cd, t, r, c) ) {
                    universe.move_agent(r,c, rd, cd, t+1)
                }
            }
        }
    }
}

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l, isself
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0 // number of neighbours
    s = 0 // number of same color neighbours
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        isself = (i==rs & j==cs)
        if (universe.has_agent(i,j,t) & !isself) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

real scalar schelling::is_happy(
    real scalar t , real scalar r , real scalar c , 
    real scalar ts, real scalar rs, real scalar cs)
{
    real   scalar prop
        
    prop   = sum_neighbours(t,r,c,ts,rs,cs)
    
    return(prop >= limit)
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

end

We can stop the loop the first time we find a appropriate cell:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prob()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// ------------------------------------------------------------ helper functions


void schelling::move(real scalar t, real scalar r, real scalar c)
{
    real scalar    radius, i, maxradius, rd, cd, stop
    real matrix    neigh
    
    stop = 0
    maxradius = max( ( rdim-r, r-1, cdim-c, c-1 ) )

    for (radius = 1 ; radius <= maxradius ; radius ++) {
        neigh = universe.find_ring(r,c,radius)
        for (i = 1; i <= rows(neigh); i++) {
            rd = neigh[i,1]
            cd = neigh[i,2]
            if (universe.has_agent(rd,cd, t+1) == 0) {
                if ( is_happy(t+1, rd, cd, t, r, c) ) {
                    universe.move_agent(r,c, rd, cd, t+1)
                    stop = 1
                    break
                }
            }
        }
        if (stop == 1) break
    }
}

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l, isself
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0 // number of neighbours
    s = 0 // number of same color neighbours
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        isself = (i==rs & j==cs)
        if (universe.has_agent(i,j,t) & !isself) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

real scalar schelling::is_happy(
    real scalar t , real scalar r , real scalar c , 
    real scalar ts, real scalar rs, real scalar cs)
{
    real   scalar prop
        
    prop   = sum_neighbours(t,r,c,ts,rs,cs)
    
    return(prop >= limit)
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

end

-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Building Agent Based Models -- the Schellings segregation model
-------------------------------------------------------------------------------

Run the simulation
First we will define the action of a single agent at each itteration:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prob()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// --------------------------------------------------------------- run the model
void schelling::step(real scalar t, real scalar r, real scalar c) 
{
    if (is_happy(t,r,c,t,r,c)==0) {
        move(t,r,c)
    }
}

// ------------------------------------------------------------ helper functions


void schelling::move(real scalar t, real scalar r, real scalar c)
{
    real scalar    radius, i, maxradius, rd, cd, stop
    real matrix    neigh
    
    stop = 0
    maxradius = max( ( rdim-r, r-1, cdim-c, c-1 ) )

    for (radius = 1 ; radius <= maxradius ; radius ++) {
        neigh = universe.find_ring(r,c,radius)
        for (i = 1; i <= rows(neigh); i++) {
            rd = neigh[i,1]
            cd = neigh[i,2]
            if (universe.has_agent(rd,cd, t+1) == 0) {
                if ( is_happy(t+1, rd, cd, t, r, c) ) {
                    universe.move_agent(r,c, rd, cd, t+1)
                    stop = 1
                    break
                }
            }
        }
        if (stop == 1) break
    }
}

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l, isself
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0 // number of neighbours
    s = 0 // number of same color neighbours
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        isself = (i==rs & j==cs)
        if (universe.has_agent(i,j,t) & !isself) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

real scalar schelling::is_happy(
    real scalar t , real scalar r , real scalar c , 
    real scalar ts, real scalar rs, real scalar cs)
{
    real   scalar prop
        
    prop   = sum_neighbours(t,r,c,ts,rs,cs)
    
    return(prop >= limit)
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

end

Next we will define the function run() that lets the user start the simulation. It starts by setting up the universe and agents, populate that universe:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prob()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// --------------------------------------------------------------- run the model
void schelling::step(real scalar t, real scalar r, real scalar c) 
{
    if (is_happy(t,r,c,t,r,c)==0) {
        move(t,r,c)
    }
}

void schelling::run() {    
    setup_universe()
    setup_agents()
    populate_universe()
    
}


// ------------------------------------------------------------ helper functions


void schelling::move(real scalar t, real scalar r, real scalar c)
{
    real scalar    radius, i, maxradius, rd, cd, stop
    real matrix    neigh
    
    stop = 0
    maxradius = max( ( rdim-r, r-1, cdim-c, c-1 ) )

    for (radius = 1 ; radius <= maxradius ; radius ++) {
        neigh = universe.find_ring(r,c,radius)
        for (i = 1; i <= rows(neigh); i++) {
            rd = neigh[i,1]
            cd = neigh[i,2]
            if (universe.has_agent(rd,cd, t+1) == 0) {
                if ( is_happy(t+1, rd, cd, t, r, c) ) {
                    universe.move_agent(r,c, rd, cd, t+1)
                    stop = 1
                    break
                }
            }
        }
        if (stop == 1) break
    }
}

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l, isself
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0 // number of neighbours
    s = 0 // number of same color neighbours
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        isself = (i==rs & j==cs)
        if (universe.has_agent(i,j,t) & !isself) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

real scalar schelling::is_happy(
    real scalar t , real scalar r , real scalar c , 
    real scalar ts, real scalar rs, real scalar cs)
{
    real   scalar prop
        
    prop   = sum_neighbours(t,r,c,ts,rs,cs)
    
    return(prop >= limit)
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

end

Than it loops over iterations, each time copying the grid to the next iteration, and than looping over the cells of that grid:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prob()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// --------------------------------------------------------------- run the model
void schelling::step(real scalar t, real scalar r, real scalar c) 
{
    if (is_happy(t,r,c,t,r,c)==0) {
        move(t,r,c)
    }
}

void schelling::run() {    
    real scalar  t, k
    real matrix coords

    setup_universe()
    setup_agents()
    populate_universe()
    
    for(t=0; t < universe.tdim() ; t++) {
        universe.copy_grid(t, t+1)
        coords = universe.schedule()
        for(k=1; k <= rows(coords); k++){

        }
    }
}


// ------------------------------------------------------------ helper functions


void schelling::move(real scalar t, real scalar r, real scalar c)
{
    real scalar    radius, i, maxradius, rd, cd, stop
    real matrix    neigh
    
    stop = 0
    maxradius = max( ( rdim-r, r-1, cdim-c, c-1 ) )

    for (radius = 1 ; radius <= maxradius ; radius ++) {
        neigh = universe.find_ring(r,c,radius)
        for (i = 1; i <= rows(neigh); i++) {
            rd = neigh[i,1]
            cd = neigh[i,2]
            if (universe.has_agent(rd,cd, t+1) == 0) {
                if ( is_happy(t+1, rd, cd, t, r, c) ) {
                    universe.move_agent(r,c, rd, cd, t+1)
                    stop = 1
                    break
                }
            }
        }
        if (stop == 1) break
    }
}

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l, isself
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0 // number of neighbours
    s = 0 // number of same color neighbours
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        isself = (i==rs & j==cs)
        if (universe.has_agent(i,j,t) & !isself) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

real scalar schelling::is_happy(
    real scalar t , real scalar r , real scalar c , 
    real scalar ts, real scalar rs, real scalar cs)
{
    real   scalar prop
        
    prop   = sum_neighbours(t,r,c,ts,rs,cs)
    
    return(prop >= limit)
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

end

If there is an agent in that cell it lets it take its step:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prob()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        // some functions to export or summarize the results
}

// --------------------------------------------------------------- run the model
void schelling::step(real scalar t, real scalar r, real scalar c) 
{
    if (is_happy(t,r,c,t,r,c)==0) {
        move(t,r,c)
    }
}

void schelling::run() {
    real scalar  t, k, r, c
    real matrix coords
    
    setup_universe()
    setup_agents()
    populate_universe()
    
    for(t=0; t < universe.tdim() ; t++) {
        universe.copy_grid(t, t+1)
        coords = universe.schedule()
        for(k=1; k <= rows(coords); k++){
            r = coords[k,1]
            c = coords[k,2]
            if (universe.has_agent(r,c,t)) {
                step(t, r, c)
            }
        }
    }
}


// ------------------------------------------------------------ helper functions


void schelling::move(real scalar t, real scalar r, real scalar c)
{
    real scalar    radius, i, maxradius, rd, cd, stop
    real matrix    neigh
    
    stop = 0
    maxradius = max( ( rdim-r, r-1, cdim-c, c-1 ) )

    for (radius = 1 ; radius <= maxradius ; radius ++) {
        neigh = universe.find_ring(r,c,radius)
        for (i = 1; i <= rows(neigh); i++) {
            rd = neigh[i,1]
            cd = neigh[i,2]
            if (universe.has_agent(rd,cd, t+1) == 0) {
                if ( is_happy(t+1, rd, cd, t, r, c) ) {
                    universe.move_agent(r,c, rd, cd, t+1)
                    stop = 1
                    break
                }
            }
        }
        if (stop == 1) break
    }
}

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l, isself
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0 // number of neighbours
    s = 0 // number of same color neighbours
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        isself = (i==rs & j==cs)
        if (universe.has_agent(i,j,t) & !isself) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

real scalar schelling::is_happy(
    real scalar t , real scalar r , real scalar c , 
    real scalar ts, real scalar rs, real scalar cs)
{
    real   scalar prop
        
    prop   = sum_neighbours(t,r,c,ts,rs,cs)
    
    return(prop >= limit)
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

end
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Building Agent Based Models -- the Schellings segregation model
-------------------------------------------------------------------------------

display summary statistics
We want to display a table that shows for each iteration the average proportion of same color neighbours and the proportion of happy agents.
Start with creating a matrix with the first column indicating the iteration:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prob()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        real matrix      summ()
}

// -------------------------------------------------------------- return results

real matrix schelling::summ()
{
    real matrix res
    
    res = J(tdim+1, 3, .)
    res[.,1] = (0..tdim)'
}

// --------------------------------------------------------------- run the model
void schelling::step(real scalar t, real scalar r, real scalar c) 
{
    if (is_happy(t,r,c,t,r,c)==0) {
        move(t,r,c)
    }
}

void schelling::run() {
    real scalar  t, k, r, c
    real matrix coords
    
    setup_universe()
    setup_agents()
    populate_universe()
    
    for(t=0; t < universe.tdim() ; t++) {
        universe.copy_grid(t, t+1)
        coords = universe.schedule()
        for(k=1; k <= rows(coords); k++){
            r = coords[k,1]
            c = coords[k,2]
            if (universe.has_agent(r,c,t)) {
                step(t, r, c)
            }
        }
    }
}


// ------------------------------------------------------------ helper functions


void schelling::move(real scalar t, real scalar r, real scalar c)
{
    real scalar    radius, i, maxradius, rd, cd, stop
    real matrix    neigh
    
    stop = 0
    maxradius = max( ( rdim-r, r-1, cdim-c, c-1 ) )

    for (radius = 1 ; radius <= maxradius ; radius ++) {
        neigh = universe.find_ring(r,c,radius)
        for (i = 1; i <= rows(neigh); i++) {
            rd = neigh[i,1]
            cd = neigh[i,2]
            if (universe.has_agent(rd,cd, t+1) == 0) {
                if ( is_happy(t+1, rd, cd, t, r, c) ) {
                    universe.move_agent(r,c, rd, cd, t+1)
                    stop = 1
                    break
                }
            }
        }
        if (stop == 1) break
    }
}

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l, isself
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0 // number of neighbours
    s = 0 // number of same color neighbours
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        isself = (i==rs & j==cs)
        if (universe.has_agent(i,j,t) & !isself) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

real scalar schelling::is_happy(
    real scalar t , real scalar r , real scalar c , 
    real scalar ts, real scalar rs, real scalar cs)
{
    real   scalar prop
        
    prop   = sum_neighbours(t,r,c,ts,rs,cs)
    
    return(prop >= limit)
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

end

Next we fill in the second column with the average proportion of same color agents:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prop()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        real matrix      summ()
}

// -------------------------------------------------------------- return results

real matrix schelling::summ()
{
    real matrix res, prop
    real scalar t, k, r, c
    
    res = J(tdim+1, 3, .)
    res[.,1] = (0..tdim)'

    for(t=0; t <= tdim ; t++){
        prop = J(nagents,1,.)
        k=1
        for (r=1; r<=rdim ; r++) {
            for (c=1 ; c <= cdim ; c++) {
                if (universe.has_agent(r,c,t)) {
                    prop[k] = sum_neighbours(t,r,c,t,r,c)
                    k = k+1
                }
            }
        }
        res[t+1, 2] = mean(prop)
    }
    return(res)
}

// --------------------------------------------------------------- run the model
void schelling::step(real scalar t, real scalar r, real scalar c) 
{
    if (is_happy(t,r,c,t,r,c)==0) {
        move(t,r,c)
    }
}

void schelling::run() {
    real scalar  t, k, r, c
    real matrix coords
    
    setup_universe()
    setup_agents()
    populate_universe()
    
    for(t=0; t < universe.tdim() ; t++) {
        universe.copy_grid(t, t+1)
        coords = universe.schedule()
        for(k=1; k <= rows(coords); k++){
            r = coords[k,1]
            c = coords[k,2]
            if (universe.has_agent(r,c,t)) {
                step(t, r, c)
            }
        }
    }
}


// ------------------------------------------------------------ helper functions


void schelling::move(real scalar t, real scalar r, real scalar c)
{
    real scalar    radius, i, maxradius, rd, cd, stop
    real matrix    neigh
    
    stop = 0
    maxradius = max( ( rdim-r, r-1, cdim-c, c-1 ) )

    for (radius = 1 ; radius <= maxradius ; radius ++) {
        neigh = universe.find_ring(r,c,radius)
        for (i = 1; i <= rows(neigh); i++) {
            rd = neigh[i,1]
            cd = neigh[i,2]
            if (universe.has_agent(rd,cd, t+1) == 0) {
                if ( is_happy(t+1, rd, cd, t, r, c) ) {
                    universe.move_agent(r,c, rd, cd, t+1)
                    stop = 1
                    break
                }
            }
        }
        if (stop == 1) break
    }
}

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l, isself
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0
    s = 0
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        isself = (i==rs & j==cs)
        if (universe.has_agent(i,j,t) & !isself) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

real scalar schelling::is_happy(
    real scalar t , real scalar r , real scalar c , 
    real scalar ts, real scalar rs, real scalar cs)
{
    real   scalar prop
        
    prop   = sum_neighbours(t,r,c,ts,rs,cs)
    
    return(prop >= limit)
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

foo = schelling()
foo.rdim(10)
foo.cdim(10)
foo.tdim(5)
foo.nred(45)
foo.nblue(45)
foo.limit(.3)
foo.run()
foo.summ()

end

Now we can fill the last column:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prop()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        real matrix      summ()
}

// -------------------------------------------------------------- return results

real matrix schelling::summ()
{
    real matrix res, prop
    real scalar t, k, r, c
    
    res = J(tdim+1, 3, .)
    res[.,1] = (0..tdim)'

    for(t=0; t <= tdim ; t++){
        prop = J(nagents,2,.)
        k=1
        for (r=1; r<=rdim ; r++) {
            for (c=1 ; c <= cdim ; c++) {
                if (universe.has_agent(r,c,t)) {
                    prop[k,1] = sum_neighbours(t,r,c,t,r,c)
                    prop[k,2] = is_happy(t,r,c,t,r,c)
                    k = k+1
                }
            }
        }
        res[|t+1, 2 \ t+1,3|] = mean(prop)
    }
    return(res)
}

// --------------------------------------------------------------- run the model
void schelling::step(real scalar t, real scalar r, real scalar c) 
{
    if (is_happy(t,r,c,t,r,c)==0) {
        move(t,r,c)
    }
}

void schelling::run() {
    real scalar  t, k, r, c
    real matrix coords
    
    setup_universe()
    setup_agents()
    populate_universe()
    
    for(t=0; t < universe.tdim() ; t++) {
        universe.copy_grid(t, t+1)
        coords = universe.schedule()
        for(k=1; k <= rows(coords); k++){
            r = coords[k,1]
            c = coords[k,2]
            if (universe.has_agent(r,c,t)) {
                step(t, r, c)
            }
        }
    }
}


// ------------------------------------------------------------ helper functions


void schelling::move(real scalar t, real scalar r, real scalar c)
{
    real scalar    radius, i, maxradius, rd, cd, stop
    real matrix    neigh
    
    stop = 0
    maxradius = max( ( rdim-r, r-1, cdim-c, c-1 ) )

    for (radius = 1 ; radius <= maxradius ; radius ++) {
        neigh = universe.find_ring(r,c,radius)
        for (i = 1; i <= rows(neigh); i++) {
            rd = neigh[i,1]
            cd = neigh[i,2]
            if (universe.has_agent(rd,cd, t+1) == 0) {
                if ( is_happy(t+1, rd, cd, t, r, c) ) {
                    universe.move_agent(r,c, rd, cd, t+1)
                    stop = 1
                    break
                }
            }
        }
        if (stop == 1) break
    }
}

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l, isself
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0
    s = 0
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        isself = (i==rs & j==cs)
        if (universe.has_agent(i,j,t) & !isself) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

real scalar schelling::is_happy(
    real scalar t , real scalar r , real scalar c , 
    real scalar ts, real scalar rs, real scalar cs)
{
    real   scalar prop
        
    prop   = sum_neighbours(t,r,c,ts,rs,cs)
    
    return(prop >= limit)
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

foo = schelling()
foo.rdim(10)
foo.cdim(10)
foo.tdim(5)
foo.nred(45)
foo.nblue(45)
foo.limit(.3)
foo.run()
foo.summ()

end

If export this matrix to Stata then we can use matlist to make this table look nice:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prop()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        real matrix      summ()
        void             summtable()
}

// -------------------------------------------------------------- return results

real matrix schelling::summ()
{
    real matrix res, prop
    real scalar t, k, r, c
    
    res = J(tdim+1, 3, .)
    res[.,1] = (0..tdim)'

    for(t=0; t <= tdim ; t++){
        prop = J(nagents,2,.)
        k=1
        for (r=1; r<=rdim ; r++) {
            for (c=1 ; c <= cdim ; c++) {
                if (universe.has_agent(r,c,t)) {
                    prop[k,1] = sum_neighbours(t,r,c,t,r,c)
                    prop[k,2] = is_happy(t,r,c,t,r,c)
                    k = k+1
                }
            }
        }
        res[|t+1, 2 \ t+1,3|] = mean(prop)
    }
    return(res)
}

void schelling::summtable(| string scalar matname)
{
    real matrix res
    string scalar cmd
    
    res = summ()
    if (args()==0) {
        matname = st_tempname()
    }
    st_matrix(matname,res)
    st_matrixcolstripe(matname, (J(3,1, "") , ("Iteration" \ "prop_same" \ "prop_happy")) )
    cmd = "matlist " + matname + ", underscore format(%10.3g) names(columns) " +
          "border(rows) " + 
          "title(Average proportion of neighbours with same color and average proportion of happy agents)"
    stata(cmd)
}
// --------------------------------------------------------------- run the model
void schelling::step(real scalar t, real scalar r, real scalar c) 
{
    if (is_happy(t,r,c,t,r,c)==0) {
        move(t,r,c)
    }
}

void schelling::run() {
    real scalar  t, k, r, c
    real matrix coords
    
    setup_universe()
    setup_agents()
    populate_universe()
    
    for(t=0; t < universe.tdim() ; t++) {
        universe.copy_grid(t, t+1)
        coords = universe.schedule()
        for(k=1; k <= rows(coords); k++){
            r = coords[k,1]
            c = coords[k,2]
            if (universe.has_agent(r,c,t)) {
                step(t, r, c)
            }
        }
    }
}


// ------------------------------------------------------------ helper functions


void schelling::move(real scalar t, real scalar r, real scalar c)
{
    real scalar    radius, i, maxradius, rd, cd, stop
    real matrix    neigh
    
    stop = 0
    maxradius = max( ( rdim-r, r-1, cdim-c, c-1 ) )

    for (radius = 1 ; radius <= maxradius ; radius ++) {
        neigh = universe.find_ring(r,c,radius)
        for (i = 1; i <= rows(neigh); i++) {
            rd = neigh[i,1]
            cd = neigh[i,2]
            if (universe.has_agent(rd,cd, t+1) == 0) {
                if ( is_happy(t+1, rd, cd, t, r, c) ) {
                    universe.move_agent(r,c, rd, cd, t+1)
                    stop = 1
                    break
                }
            }
        }
        if (stop == 1) break
    }
}

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l, isself
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0
    s = 0
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        isself = (i==rs & j==cs)
        if (universe.has_agent(i,j,t) & !isself) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

real scalar schelling::is_happy(
    real scalar t , real scalar r , real scalar c , 
    real scalar ts, real scalar rs, real scalar cs)
{
    real   scalar prop
        
    prop   = sum_neighbours(t,r,c,ts,rs,cs)
    
    return(prop >= limit)
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

foo = schelling()
foo.rdim(10)
foo.cdim(10)
foo.tdim(5)
foo.nred(45)
foo.nblue(45)
foo.limit(.3)
foo.run()
foo.summtable()

end
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Building Agent Based Models -- the Schellings segregation model
-------------------------------------------------------------------------------

Export to Stata
To plot the agents on the grid we first need to export it to Stata. We would like for each cell with an agent the:
row number column number iteration number id of that agent the color of that agent whether or not the agent is happy
So this function must receive 6 names of variables that are to be created
With the abm_grid class we can extract the row, column, iteration number and the id, though the 4{sup:th} column is superfluous: That contains the location within the cell, but we only allowed one agent per cell
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prop()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        real matrix      summ()
        void             summtable()
        void             extract()
}

// -------------------------------------------------------------- return results

void schelling::extract(string rowvector names)
{
    real matrix res
    
    if(cols(names)!=6){
        _error(3300, "names must contain 6 columns")
    }
    
    res = universe.extract()
    
    res = res[.,1..3], res[.,5]
}

real matrix schelling::summ()
{
    real matrix res, prop
    real scalar t, k, r, c
    
    res = J(tdim+1, 3, .)
    res[.,1] = (0..tdim)'

    for(t=0; t <= tdim ; t++){
        prop = J(nagents,2,.)
        k=1
        for (r=1; r<=rdim ; r++) {
            for (c=1 ; c <= cdim ; c++) {
                if (universe.has_agent(r,c,t)) {
                    prop[k,1] = sum_neighbours(t,r,c,t,r,c)
                    prop[k,2] = is_happy(t,r,c,t,r,c)
                    k = k+1
                }
            }
        }
        res[|t+1, 2 \ t+1,3|] = mean(prop)
    }
    return(res)
}

void schelling::summtable(| string scalar matname)
{
    real matrix res
    string scalar cmd
    
    res = summ()
    if (args()==0) {
        matname = st_tempname()
    }
    st_matrix(matname,res)
    st_matrixcolstripe(matname, (J(3,1, "") , ("Iteration" \ "prop_same" \ "prop_happy")) )
    cmd = "matlist " + matname + ", underscore format(%10.3g) names(columns) " +
          "border(rows) " + 
          "title(Average proportion of neighbours with same color and average proportion of happy agents)"
    stata(cmd)
}
// --------------------------------------------------------------- run the model
void schelling::step(real scalar t, real scalar r, real scalar c) 
{
    if (is_happy(t,r,c,t,r,c)==0) {
        move(t,r,c)
    }
}

void schelling::run() {
    real scalar  t, k, r, c
    real matrix coords
    
    setup_universe()
    setup_agents()
    populate_universe()
    
    for(t=0; t < universe.tdim() ; t++) {
        universe.copy_grid(t, t+1)
        coords = universe.schedule()
        for(k=1; k <= rows(coords); k++){
            r = coords[k,1]
            c = coords[k,2]
            if (universe.has_agent(r,c,t)) {
                step(t, r, c)
            }
        }
    }
}


// ------------------------------------------------------------ helper functions


void schelling::move(real scalar t, real scalar r, real scalar c)
{
    real scalar    radius, i, maxradius, rd, cd, stop
    real matrix    neigh
    
    stop = 0
    maxradius = max( ( rdim-r, r-1, cdim-c, c-1 ) )

    for (radius = 1 ; radius <= maxradius ; radius ++) {
        neigh = universe.find_ring(r,c,radius)
        for (i = 1; i <= rows(neigh); i++) {
            rd = neigh[i,1]
            cd = neigh[i,2]
            if (universe.has_agent(rd,cd, t+1) == 0) {
                if ( is_happy(t+1, rd, cd, t, r, c) ) {
                    universe.move_agent(r,c, rd, cd, t+1)
                    stop = 1
                    break
                }
            }
        }
        if (stop == 1) break
    }
}

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l, isself
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0
    s = 0
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        isself = (i==rs & j==cs)
        if (universe.has_agent(i,j,t) & !isself) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

real scalar schelling::is_happy(
    real scalar t , real scalar r , real scalar c , 
    real scalar ts, real scalar rs, real scalar cs)
{
    real   scalar prop
        
    prop   = sum_neighbours(t,r,c,ts,rs,cs)
    
    return(prop >= limit)
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

foo = schelling()
foo.rdim(10)
foo.cdim(10)
foo.tdim(5)
foo.nred(45)
foo.nblue(45)
foo.limit(.3)
foo.run()
foo.summtable()

end

The 5{sup:th} column is the id of the agent, which we can use to recover the color from the associative array agents:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prop()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        real matrix      summ()
        void             summtable()
        void             extract()
}

// -------------------------------------------------------------- return results

void schelling::extract(string rowvector names)
{
    real matrix res
    real colvector color
    real scalar id, i
    
    if(cols(names)!=6){
        _error(3300, "names must contain 6 columns")
    }
    
    res = universe.extract()
    color = J(rows(res),1,.)
    for (i= 1 ; i <= rows(res) ; i++) {
        id = res[i,5]
        color[i] = ( agents.get(id) == "red" )
    }
    
    res = res[.,1..3], res[.,5], color
}

real matrix schelling::summ()
{
    real matrix res, prop
    real scalar t, k, r, c
    
    res = J(tdim+1, 3, .)
    res[.,1] = (0..tdim)'

    for(t=0; t <= tdim ; t++){
        prop = J(nagents,2,.)
        k=1
        for (r=1; r<=rdim ; r++) {
            for (c=1 ; c <= cdim ; c++) {
                if (universe.has_agent(r,c,t)) {
                    prop[k,1] = sum_neighbours(t,r,c,t,r,c)
                    prop[k,2] = is_happy(t,r,c,t,r,c)
                    k = k+1
                }
            }
        }
        res[|t+1, 2 \ t+1,3|] = mean(prop)
    }
    return(res)
}

void schelling::summtable(| string scalar matname)
{
    real matrix res
    string scalar cmd
    
    res = summ()
    if (args()==0) {
        matname = st_tempname()
    }
    st_matrix(matname,res)
    st_matrixcolstripe(matname, (J(3,1, "") , ("Iteration" \ "prop_same" \ "prop_happy")) )
    cmd = "matlist " + matname + ", underscore format(%10.3g) names(columns) " +
          "border(rows) " + 
          "title(Average proportion of neighbours with same color and average proportion of happy agents)"
    stata(cmd)
}
// --------------------------------------------------------------- run the model
void schelling::step(real scalar t, real scalar r, real scalar c) 
{
    if (is_happy(t,r,c,t,r,c)==0) {
        move(t,r,c)
    }
}

void schelling::run() {
    real scalar  t, k, r, c
    real matrix coords
    
    setup_universe()
    setup_agents()
    populate_universe()
    
    for(t=0; t < universe.tdim() ; t++) {
        universe.copy_grid(t, t+1)
        coords = universe.schedule()
        for(k=1; k <= rows(coords); k++){
            r = coords[k,1]
            c = coords[k,2]
            if (universe.has_agent(r,c,t)) {
                step(t, r, c)
            }
        }
    }
}


// ------------------------------------------------------------ helper functions


void schelling::move(real scalar t, real scalar r, real scalar c)
{
    real scalar    radius, i, maxradius, rd, cd, stop
    real matrix    neigh
    
    stop = 0
    maxradius = max( ( rdim-r, r-1, cdim-c, c-1 ) )

    for (radius = 1 ; radius <= maxradius ; radius ++) {
        neigh = universe.find_ring(r,c,radius)
        for (i = 1; i <= rows(neigh); i++) {
            rd = neigh[i,1]
            cd = neigh[i,2]
            if (universe.has_agent(rd,cd, t+1) == 0) {
                if ( is_happy(t+1, rd, cd, t, r, c) ) {
                    universe.move_agent(r,c, rd, cd, t+1)
                    stop = 1
                    break
                }
            }
        }
        if (stop == 1) break
    }
}

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l, isself
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0
    s = 0
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        isself = (i==rs & j==cs)
        if (universe.has_agent(i,j,t) & !isself) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

real scalar schelling::is_happy(
    real scalar t , real scalar r , real scalar c , 
    real scalar ts, real scalar rs, real scalar cs)
{
    real   scalar prop
        
    prop   = sum_neighbours(t,r,c,ts,rs,cs)
    
    return(prop >= limit)
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

foo = schelling()
foo.rdim(10)
foo.cdim(10)
foo.tdim(5)
foo.nred(45)
foo.nblue(45)
foo.limit(.3)
foo.run()
foo.summtable()

end

We can now add the whether or not the agent is happy:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prop()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        real matrix      summ()
        void             summtable()
        void             extract()
}

// -------------------------------------------------------------- return results

void schelling::extract(string rowvector names)
{
    real matrix res
    real colvector color, happy
    real scalar id, i, t, r, c
    
    if(cols(names)!=6){
        _error(3300, "names must contain 6 columns")
    }
    
    res = universe.extract()
    color = J(rows(res),1,.)
    happy = J(rows(res),1,.)
    for (i= 1 ; i <= rows(res) ; i++) {
        id = res[i,5]
        color[i] = ( agents.get(id) == "red" )
        r = res[i,1]
        c = res[i,2]
        t = res[i,3]
        happy[i] = is_happy(t,r,c,t,r,c)
    }
    
    res = res[.,1..3], res[.,5], color, happy
}

real matrix schelling::summ()
{
    real matrix res, prop
    real scalar t, k, r, c
    
    res = J(tdim+1, 3, .)
    res[.,1] = (0..tdim)'

    for(t=0; t <= tdim ; t++){
        prop = J(nagents,2,.)
        k=1
        for (r=1; r<=rdim ; r++) {
            for (c=1 ; c <= cdim ; c++) {
                if (universe.has_agent(r,c,t)) {
                    prop[k,1] = sum_neighbours(t,r,c,t,r,c)
                    prop[k,2] = is_happy(t,r,c,t,r,c)
                    k = k+1
                }
            }
        }
        res[|t+1, 2 \ t+1,3|] = mean(prop)
    }
    return(res)
}

void schelling::summtable(| string scalar matname)
{
    real matrix res
    string scalar cmd
    
    res = summ()
    if (args()==0) {
        matname = st_tempname()
    }
    st_matrix(matname,res)
    st_matrixcolstripe(matname, (J(3,1, "") , ("Iteration" \ "prop_same" \ "prop_happy")) )
    cmd = "matlist " + matname + ", underscore format(%10.3g) names(columns) " +
          "border(rows) " + 
          "title(Average proportion of neighbours with same color and average proportion of happy agents)"
    stata(cmd)
}
// --------------------------------------------------------------- run the model
void schelling::step(real scalar t, real scalar r, real scalar c) 
{
    if (is_happy(t,r,c,t,r,c)==0) {
        move(t,r,c)
    }
}

void schelling::run() {
    real scalar  t, k, r, c
    real matrix coords
    
    setup_universe()
    setup_agents()
    populate_universe()
    
    for(t=0; t < universe.tdim() ; t++) {
        universe.copy_grid(t, t+1)
        coords = universe.schedule()
        for(k=1; k <= rows(coords); k++){
            r = coords[k,1]
            c = coords[k,2]
            if (universe.has_agent(r,c,t)) {
                step(t, r, c)
            }
        }
    }
}


// ------------------------------------------------------------ helper functions


void schelling::move(real scalar t, real scalar r, real scalar c)
{
    real scalar    radius, i, maxradius, rd, cd, stop
    real matrix    neigh
    
    stop = 0
    maxradius = max( ( rdim-r, r-1, cdim-c, c-1 ) )

    for (radius = 1 ; radius <= maxradius ; radius ++) {
        neigh = universe.find_ring(r,c,radius)
        for (i = 1; i <= rows(neigh); i++) {
            rd = neigh[i,1]
            cd = neigh[i,2]
            if (universe.has_agent(rd,cd, t+1) == 0) {
                if ( is_happy(t+1, rd, cd, t, r, c) ) {
                    universe.move_agent(r,c, rd, cd, t+1)
                    stop = 1
                    break
                }
            }
        }
        if (stop == 1) break
    }
}

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l, isself
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0
    s = 0
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        isself = (i==rs & j==cs)
        if (universe.has_agent(i,j,t) & !isself) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

real scalar schelling::is_happy(
    real scalar t , real scalar r , real scalar c , 
    real scalar ts, real scalar rs, real scalar cs)
{
    real   scalar prop
        
    prop   = sum_neighbours(t,r,c,ts,rs,cs)
    
    return(prop >= limit)
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

foo = schelling()
foo.rdim(10)
foo.cdim(10)
foo.tdim(5)
foo.nred(45)
foo.nblue(45)
foo.limit(.3)
foo.run()
foo.summtable()

end

Now we need to store that matrix as a Stata dataset:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prop()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        real matrix      summ()
        void             summtable()
        void             extract()
}

// -------------------------------------------------------------- return results

void schelling::extract(string rowvector names)
{
    real matrix res
    real colvector color, happy
    real rowvector idx
    real scalar id, i, t, r, c
    
    if(cols(names)!=6){
        _error(3300, "names must contain 6 columns")
    }
    
    res = universe.extract()
    color = J(rows(res),1,.)
    happy = J(rows(res),1,.)
    for (i= 1 ; i <= rows(res) ; i++) {
        id = res[i,5]
        color[i] = ( agents.get(id) == "red" )
        r = res[i,1]
        c = res[i,2]
        t = res[i,3]
        happy[i] = is_happy(t,r,c,t,r,c)
    }
    
    res = res[.,1..3], res[.,5], color, happy
    
    idx = st_addvar("float", names)

    if (st_nobs() < rows(res) ) {
        st_addobs(rows(res) - st_nobs())
    }
    st_store(.,idx,res)
}

real matrix schelling::summ()
{
    real matrix res, prop
    real scalar t, k, r, c
    
    res = J(tdim+1, 3, .)
    res[.,1] = (0..tdim)'

    for(t=0; t <= tdim ; t++){
        prop = J(nagents,2,.)
        k=1
        for (r=1; r<=rdim ; r++) {
            for (c=1 ; c <= cdim ; c++) {
                if (universe.has_agent(r,c,t)) {
                    prop[k,1] = sum_neighbours(t,r,c,t,r,c)
                    prop[k,2] = is_happy(t,r,c,t,r,c)
                    k = k+1
                }
            }
        }
        res[|t+1, 2 \ t+1,3|] = mean(prop)
    }
    return(res)
}

void schelling::summtable(| string scalar matname)
{
    real matrix res
    string scalar cmd
    
    res = summ()
    if (args()==0) {
        matname = st_tempname()
    }
    st_matrix(matname,res)
    st_matrixcolstripe(matname, (J(3,1, "") , ("Iteration" \ "prop_same" \ "prop_happy")) )
    cmd = "matlist " + matname + ", underscore format(%10.3g) names(columns) " +
          "border(rows) " + 
          "title(Average proportion of neighbours with same color and average proportion of happy agents)"
    stata(cmd)
}
// --------------------------------------------------------------- run the model
void schelling::step(real scalar t, real scalar r, real scalar c) 
{
    if (is_happy(t,r,c,t,r,c)==0) {
        move(t,r,c)
    }
}

void schelling::run() {
    real scalar  t, k, r, c
    real matrix coords
    
    setup_universe()
    setup_agents()
    populate_universe()
    
    for(t=0; t < universe.tdim() ; t++) {
        universe.copy_grid(t, t+1)
        coords = universe.schedule()
        for(k=1; k <= rows(coords); k++){
            r = coords[k,1]
            c = coords[k,2]
            if (universe.has_agent(r,c,t)) {
                step(t, r, c)
            }
        }
    }
}


// ------------------------------------------------------------ helper functions


void schelling::move(real scalar t, real scalar r, real scalar c)
{
    real scalar    radius, i, maxradius, rd, cd, stop
    real matrix    neigh
    
    stop = 0
    maxradius = max( ( rdim-r, r-1, cdim-c, c-1 ) )

    for (radius = 1 ; radius <= maxradius ; radius ++) {
        neigh = universe.find_ring(r,c,radius)
        for (i = 1; i <= rows(neigh); i++) {
            rd = neigh[i,1]
            cd = neigh[i,2]
            if (universe.has_agent(rd,cd, t+1) == 0) {
                if ( is_happy(t+1, rd, cd, t, r, c) ) {
                    universe.move_agent(r,c, rd, cd, t+1)
                    stop = 1
                    break
                }
            }
        }
        if (stop == 1) break
    }
}

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l, isself
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0
    s = 0
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        isself = (i==rs & j==cs)
        if (universe.has_agent(i,j,t) & !isself) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

real scalar schelling::is_happy(
    real scalar t , real scalar r , real scalar c , 
    real scalar ts, real scalar rs, real scalar cs)
{
    real   scalar prop
        
    prop   = sum_neighbours(t,r,c,ts,rs,cs)
    
    return(prop >= limit)
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

foo = schelling()
foo.rdim(10)
foo.cdim(10)
foo.tdim(5)
foo.nred(45)
foo.nblue(45)
foo.limit(.3)
foo.run()
foo.summtable()

end
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Building Agent Based Models -- the Schellings segregation model
-------------------------------------------------------------------------------

Plot the grid
We can use {cmd twoway scatter} to plot the grid for the initial iteration:
clear mata
drop _all

run abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
	private:
		class grid             scalar universe
		class AssociativeArray scalar agents

		real   scalar    rdim
		real   scalar    cdim
		real   scalar    tdim
		real   scalar    limit
		real   scalar    nblue
		real   scalar    nred
		real   scalar    nagents
		
		void             is_posint()
		void             is_prop()
		
		void             setup_agents()
		void             setup_universe()		
		void             populate_universe()
		
		string scalar    get_color()
		real   scalar    sum_neighbours()	
		real   scalar    is_happy()
		void             move()		
		void             step()	
		

	
	public:
		transmorphic     rdim()
		transmorphic     cdim()
		transmorphic     tdim()
		transmorphic     limit()
		transmorphic     nblue()
		transmorphic     nred()
		
		void             run()
		
		void             extract()
		real matrix      summ()
		void             summtable()
}


// -------------------------------------------------------------- error messages
void schelling::is_posint(real scalar val, string scalar name) 
{
	string scalar errmsg
	
	if (val <= 0 | mod(val,1)!= 0 ){
		errmsg = name + " must be a positive integer"
		_error(3300, errmsg)
	}
}
void schelling::is_prop(real scalar val, string scalar name) 
{
	string scalar errmsg
	
	if (val < 0 | val > 1 ){
		errmsg = name + " must be between or including 0 and 1"
		_error(3300, errmsg)
	}
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
	if (args() == 1) {
		is_posint(dim,"dim")
		universe.rdim(dim)
		rdim = dim
	}
	else {
		return(universe.rdim())
	}
}
transmorphic schelling::cdim(| real scalar dim) 
{
	if (args() == 1) {
		is_posint(dim,"dim")
		universe.cdim(dim)
		cdim = dim
	}
	else {
		return(universe.cdim())
	}
}
transmorphic schelling::tdim(| real scalar dim) 
{
	if (args() == 1) {
		is_posint(dim,"dim")
		universe.tdim(dim)
		tdim = dim
	}
	else {
		return(universe.tdim())
	}
}
void schelling::setup_universe()
{
	if (tdim()==.) {
		_error(3000, "tdim must be set first")
	}
	universe.setup()
}	

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
	if (args() == 1) {
		is_prop(val,"value")
		limit = val
	}
	else {
		return(limit)
	}
}
transmorphic schelling::nblue(| real scalar val) 
{
	if (args() == 1) {
		is_posint(val,"value")
		nblue = val
	}
	else {
		return(nblue)
	}
}
transmorphic schelling::nred(| real scalar val) 
{
	if (args() == 1) {
		is_posint(val,"value")
		nred = val
	}
	else {
		return(nred)
	}
}
void schelling::setup_agents() 
{
	real   scalar k, i
	string scalar errmsg
	
	if (limit == .) {
		_error(3300, "limit has not been set")
	}
	if (nblue == .) {
		_error(3300, "number of blue agents has not been set")
	}
	if (nred == .) {
		_error(3300, "number of red agents has not been set")
	}
	nagents = nred + nblue
	if (nagents > universe.size()) {
		errmsg = "you specified " + strofreal(nagents) + " agents on a " +
		         strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
		_error(3300, errmsg)
	}

	agents.reinit("real")
	
	k = 0
	for (i = 1 ; i <= nred ; i++) {
		k = k +1
		agents.put(k, "red")
	}
	for (i = 1 ; i <= nblue ; i++) {
		k = k +1
		agents.put(k, "blue")
	}
}

// --------------------------------------------------------------- run the model
void schelling::populate_universe(){
	real matrix grid0
	real scalar i, r, c
	
	grid0 = universe.schedule()
	_jumble(grid0)
	for(i = 1 ; i <= nagents ; i++) {
		r = grid0[i,1]
		c = grid0[i,2]

		universe.create_agent(r,c,i,0)
	}
}


void schelling::step(real scalar t, real scalar r, real scalar c) 
{
	if (is_happy(t,r,c,t,r,c)==0) {
		move(t,r,c)
	}
}

void schelling::run() {
	real scalar  t, k, r, c
	real matrix coords
	
	setup_universe()
	setup_agents()
	populate_universe()
	
	for(t=0; t < universe.tdim() ; t++) {
		universe.copy_grid(t, t+1)
		coords = universe.schedule()
		for(k=1; k <= rows(coords); k++){
			r = coords[k,1]
			c = coords[k,2]
			if (universe.has_agent(r,c,t)) {
				step(t, r, c)
			}
		}
	}
}

// ------------------------------------------------------------ helper functions

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
	real scalar    id
	
	if (universe.has_agent(r,c,t)) {
		id = universe.agent_id(r, c,t)
	}
	else {
		_error(3300, "no agent in this cell")
	}
	return(agents.get(id))
}

real scalar schelling::is_happy(
	real scalar t , real scalar r , real scalar c , 
	real scalar ts, real scalar rs, real scalar cs)
{
	real   scalar prop
		
	prop   = sum_neighbours(t,r,c,ts,rs,cs)
	
	return(prop >= limit)
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
								real scalar ts, real scalar rs, real scalar cs) 
{
	real   scalar    k, s, i, j, l, isself
	real   matrix    neigh
	string scalar    self
	
	self = get_color(ts,rs,cs)
	
	neigh = universe.find_ring(r,c,1)
	k = 0
	s = 0
	for (l=1 ; l <=rows(neigh) ; l++) {
		i = neigh[l,1]
		j = neigh[l,2]
		isself = (i==rs & j==cs)
		if (universe.has_agent(i,j,t) & !isself) {
			k = k + 1
			s = s + (get_color(t,i,j)==self)
		}
	}
	return((k==0 ? 1 : s/k))
}

void schelling::move(real scalar t, real scalar r, real scalar c)
{
	real scalar    radius, i, stop, rd, cd, maxradius
	real matrix    neigh
	
	stop = 0
	maxradius = max( ( rdim-r, r-1, cdim-c, c-1 ) )

	for (radius = 1 ; radius <= maxradius ; radius ++) {
		neigh = universe.find_ring(r,c,radius)
		for (i = 1; i <= rows(neigh); i++) {
			rd = neigh[i,1]
			cd = neigh[i,2]
			if (universe.has_agent(rd,cd, t+1,) == 0) {
				if( is_happy(t+1,rd, cd, t, r, c) ) {
					universe.move_agent(r,c, rd, cd, t+1)
					stop = 1
					break
				}
			}
		}
		if (stop == 1) break
	}
}

// ---------------------------------------------------------------------- export
void schelling::extract(string rowvector names)
{
	real matrix res
	real colvector color, happy
	real rowvector idx
	real scalar i, k, t,r,c
	
	if(cols(names)!=6){
		_error(3300, "names must contain 6 columns")
	}
	
	res = universe.extract()
	color = J(rows(res),1,.)
	happy = J(rows(res),1,.)
	k = cols(res)
	for (i = 1 ; i <= rows(res); i++) {
		color[i] = agents.get(res[i,k]) == "red"
		t = res[i,3]
		r = res[i,1]
		c = res[i,2]
		happy[i] = is_happy(t,r,c,t,r,c)
	}
	res = res[.,1..3], res[.,5], color, happy
	
	idx = st_addvar("float", names)

	if (st_nobs() < rows(res) ) {
		st_addobs(rows(res) - st_nobs())
	}
	st_store(.,idx,res)
}

real matrix schelling::summ()
{
	real matrix res, prop
	real scalar t, r, c, k
	res = J(tdim+1,3,.)
	res[.,1] = (0 .. tdim)'

	for(t=0 ; t<= tdim; t++) {
		prop = J(nagents, 2, .)
		k = 1
		for(r=1; r <= rdim; r++) {
			for (c=1 ; c<=cdim ; c++ ) {
				if (universe.has_agent(r,c,t)) {
					prop[k,1] = sum_neighbours(t,r,c, t, r, c)
					prop[k,2] = is_happy(t,r,c,t,r,c)
					k = k + 1
				}
			}
		}
		res[|t+1,2 \ t+1,3|] = mean(prop)
	}
	return(res)
}


void schelling::summtable(| string scalar matname)
{
	real matrix res
	string scalar cmd
	
	res = summ()
	if (args()==0) {
		matname = st_tempname()
	}
	st_matrix(matname,res)
	st_matrixcolstripe(matname, (J(3,1, "") , ("Iteration" \ "prop_same" \ "prop_happy")) )
	cmd = "matlist " + matname + ", underscore format(%10.3g) names(columns) " +
          "border(rows) " + 
		  "title(Average proportion of neighbours with same color and average proportion of happy agents)"
	stata(cmd)
}


end


There are still some improvements necessary:
We don't need to label the axes It is a square grid, so the graph should also be square We could add lines to highlight the cells
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prop()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        real matrix      summ()
        void             summtable()
        void             extract()
}

// -------------------------------------------------------------- return results

void schelling::extract(string rowvector names)
{
    real matrix res
    real colvector color, happy
    real rowvector idx
    real scalar id, i, t, r, c
    
    if(cols(names)!=6){
        _error(3300, "names must contain 6 columns")
    }
    
    res = universe.extract()
    color = J(rows(res),1,.)
    happy = J(rows(res),1,.)
    for (i= 1 ; i <= rows(res) ; i++) {
        id = res[i,5]
        color[i] = ( agents.get(id) == "red" )
        r = res[i,1]
        c = res[i,2]
        t = res[i,3]
        happy[i] = is_happy(t,r,c,t,r,c)
    }
    
    res = res[.,1..3], res[.,5], color, happy
    
    idx = st_addvar("float", names)

    if (st_nobs() < rows(res) ) {
        st_addobs(rows(res) - st_nobs())
    }
    st_store(.,idx,res)
}

real matrix schelling::summ()
{
    real matrix res, prop
    real scalar t, k, r, c
    
    res = J(tdim+1, 3, .)
    res[.,1] = (0..tdim)'

    for(t=0; t <= tdim ; t++){
        prop = J(nagents,2,.)
        k=1
        for (r=1; r<=rdim ; r++) {
            for (c=1 ; c <= cdim ; c++) {
                if (universe.has_agent(r,c,t)) {
                    prop[k,1] = sum_neighbours(t,r,c,t,r,c)
                    prop[k,2] = is_happy(t,r,c,t,r,c)
                    k = k+1
                }
            }
        }
        res[|t+1, 2 \ t+1,3|] = mean(prop)
    }
    return(res)
}

void schelling::summtable(| string scalar matname)
{
    real matrix res
    string scalar cmd
    
    res = summ()
    if (args()==0) {
        matname = st_tempname()
    }
    st_matrix(matname,res)
    st_matrixcolstripe(matname, (J(3,1, "") , ("Iteration" \ "prop_same" \ "prop_happy")) )
    cmd = "matlist " + matname + ", underscore format(%10.3g) names(columns) " +
          "border(rows) " + 
          "title(Average proportion of neighbours with same color and average proportion of happy agents)"
    stata(cmd)
}
// --------------------------------------------------------------- run the model
void schelling::step(real scalar t, real scalar r, real scalar c) 
{
    if (is_happy(t,r,c,t,r,c)==0) {
        move(t,r,c)
    }
}

void schelling::run() {
    real scalar  t, k, r, c
    real matrix coords
    
    setup_universe()
    setup_agents()
    populate_universe()
    
    for(t=0; t < universe.tdim() ; t++) {
        universe.copy_grid(t, t+1)
        coords = universe.schedule()
        for(k=1; k <= rows(coords); k++){
            r = coords[k,1]
            c = coords[k,2]
            if (universe.has_agent(r,c,t)) {
                step(t, r, c)
            }
        }
    }
}


// ------------------------------------------------------------ helper functions


void schelling::move(real scalar t, real scalar r, real scalar c)
{
    real scalar    radius, i, maxradius, rd, cd, stop
    real matrix    neigh
    
    stop = 0
    maxradius = max( ( rdim-r, r-1, cdim-c, c-1 ) )

    for (radius = 1 ; radius <= maxradius ; radius ++) {
        neigh = universe.find_ring(r,c,radius)
        for (i = 1; i <= rows(neigh); i++) {
            rd = neigh[i,1]
            cd = neigh[i,2]
            if (universe.has_agent(rd,cd, t+1) == 0) {
                if ( is_happy(t+1, rd, cd, t, r, c) ) {
                    universe.move_agent(r,c, rd, cd, t+1)
                    stop = 1
                    break
                }
            }
        }
        if (stop == 1) break
    }
}

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l, isself
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0
    s = 0
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        isself = (i==rs & j==cs)
        if (universe.has_agent(i,j,t) & !isself) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

real scalar schelling::is_happy(
    real scalar t , real scalar r , real scalar c , 
    real scalar ts, real scalar rs, real scalar cs)
{
    real   scalar prop
        
    prop   = sum_neighbours(t,r,c,ts,rs,cs)
    
    return(prop >= limit)
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

foo = schelling()
foo.rdim(10)
foo.cdim(10)
foo.tdim(5)
foo.nred(45)
foo.nblue(45)
foo.limit(.3)
foo.run()
foo.summtable()
foo.extract(("r","c","t", "id", "red", "happy"))
end

replace r = -r

twoway scatter r c if red == 0 & t == 0, ///
       msymbol(O) mcolor(blue)        || ///
       scatter r c if red == 1 & t == 0, ///
       msymbol(O) mcolor(red)            ///
       yscale(off range(-10.5 -.5))      ///
       xscale(off  range(.5 10.5))       ///
       ylab(none) xlab(none)             ///
       plotregion(margin(zero))          ///
       aspect(1)                         ///
       xline(.5(1)9.5)                   ///
       yline(-.5(-1)-9.5)                ///
       legend(off)
       

We can improve the graph further:
We can distinguish between happy and non-happy agents We can add the agent id
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prop()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        real matrix      summ()
        void             summtable()
        void             extract()
}

// -------------------------------------------------------------- return results

void schelling::extract(string rowvector names)
{
    real matrix res
    real colvector color, happy
    real rowvector idx
    real scalar id, i, t, r, c
    
    if(cols(names)!=6){
        _error(3300, "names must contain 6 columns")
    }
    
    res = universe.extract()
    color = J(rows(res),1,.)
    happy = J(rows(res),1,.)
    for (i= 1 ; i <= rows(res) ; i++) {
        id = res[i,5]
        color[i] = ( agents.get(id) == "red" )
        r = res[i,1]
        c = res[i,2]
        t = res[i,3]
        happy[i] = is_happy(t,r,c,t,r,c)
    }
    
    res = res[.,1..3], res[.,5], color, happy
    
    idx = st_addvar("float", names)

    if (st_nobs() < rows(res) ) {
        st_addobs(rows(res) - st_nobs())
    }
    st_store(.,idx,res)
}

real matrix schelling::summ()
{
    real matrix res, prop
    real scalar t, k, r, c
    
    res = J(tdim+1, 3, .)
    res[.,1] = (0..tdim)'

    for(t=0; t <= tdim ; t++){
        prop = J(nagents,2,.)
        k=1
        for (r=1; r<=rdim ; r++) {
            for (c=1 ; c <= cdim ; c++) {
                if (universe.has_agent(r,c,t)) {
                    prop[k,1] = sum_neighbours(t,r,c,t,r,c)
                    prop[k,2] = is_happy(t,r,c,t,r,c)
                    k = k+1
                }
            }
        }
        res[|t+1, 2 \ t+1,3|] = mean(prop)
    }
    return(res)
}

void schelling::summtable(| string scalar matname)
{
    real matrix res
    string scalar cmd
    
    res = summ()
    if (args()==0) {
        matname = st_tempname()
    }
    st_matrix(matname,res)
    st_matrixcolstripe(matname, (J(3,1, "") , ("Iteration" \ "prop_same" \ "prop_happy")) )
    cmd = "matlist " + matname + ", underscore format(%10.3g) names(columns) " +
          "border(rows) " + 
          "title(Average proportion of neighbours with same color and average proportion of happy agents)"
    stata(cmd)
}
// --------------------------------------------------------------- run the model
void schelling::step(real scalar t, real scalar r, real scalar c) 
{
    if (is_happy(t,r,c,t,r,c)==0) {
        move(t,r,c)
    }
}

void schelling::run() {
    real scalar  t, k, r, c
    real matrix coords
    
    setup_universe()
    setup_agents()
    populate_universe()
    
    for(t=0; t < universe.tdim() ; t++) {
        universe.copy_grid(t, t+1)
        coords = universe.schedule()
        for(k=1; k <= rows(coords); k++){
            r = coords[k,1]
            c = coords[k,2]
            if (universe.has_agent(r,c,t)) {
                step(t, r, c)
            }
        }
    }
}


// ------------------------------------------------------------ helper functions


void schelling::move(real scalar t, real scalar r, real scalar c)
{
    real scalar    radius, i, maxradius, rd, cd, stop
    real matrix    neigh
    
    stop = 0
    maxradius = max( ( rdim-r, r-1, cdim-c, c-1 ) )

    for (radius = 1 ; radius <= maxradius ; radius ++) {
        neigh = universe.find_ring(r,c,radius)
        for (i = 1; i <= rows(neigh); i++) {
            rd = neigh[i,1]
            cd = neigh[i,2]
            if (universe.has_agent(rd,cd, t+1) == 0) {
                if ( is_happy(t+1, rd, cd, t, r, c) ) {
                    universe.move_agent(r,c, rd, cd, t+1)
                    stop = 1
                    break
                }
            }
        }
        if (stop == 1) break
    }
}

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l, isself
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0
    s = 0
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        isself = (i==rs & j==cs)
        if (universe.has_agent(i,j,t) & !isself) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

real scalar schelling::is_happy(
    real scalar t , real scalar r , real scalar c , 
    real scalar ts, real scalar rs, real scalar cs)
{
    real   scalar prop
        
    prop   = sum_neighbours(t,r,c,ts,rs,cs)
    
    return(prop >= limit)
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

foo = schelling()
foo.rdim(10)
foo.cdim(10)
foo.tdim(5)
foo.nred(45)
foo.nblue(45)
foo.limit(.3)
foo.run()
foo.summtable()
foo.extract(("r","c","t", "id", "red", "happy"))
end

replace r = -r

twoway scatter r c if red == 0 & t == 0 & happy == 1, ///
       msymbol(O) mcolor(blue) msize(*2)              ///
       mlabel(id) mlabpos(0)                       || ///
       scatter r c if red == 1 & t == 0 & happy == 1, ///
       msymbol(O) mcolor(red) msize(*2)               ///
       mlabel(id) mlabpos(0)                       || ///
       scatter r c if red == 0 & t == 0 & happy == 0, ///
       msymbol(Oh) mcolor(blue) msize(*2)             ///
       mlabel(id) mlabpos(0)                       || ///
       scatter r c if red == 1 & t == 0 & happy == 0, ///
       msymbol(Oh) mcolor(red) msize(*2)              ///
       mlabel(id) mlabpos(0)                          ///
       yscale(off range(-10.5 -.5))                   ///
       xscale(off  range(.5 10.5))                    ///
       ylab(none) xlab(none)                          ///
       plotregion(margin(zero))                       ///
       aspect(1)                                      ///
       xline(.5(1)9.5)                                ///
       yline(-.5(-1)-9.5)                             ///
       legend(off)
       

Now we can use a loop to create the same graph for all iterations:
clear all

do abm_grid.mata

mata
mata set matastrict on

class grid extends abm_grid
{

}

class schelling {
    private:
        // all variables are private
        class grid             scalar universe
        class AssociativeArray scalar agents

        real   scalar    rdim
        real   scalar    cdim
        real   scalar    tdim
        real   scalar    limit
        real   scalar    nblue
        real   scalar    nred
        real   scalar    nagents
        
        void             is_posint()
        void             is_prop()
        
        // initialize the model
        void             setup_agents()
        void             setup_universe()        
        void             populate_universe()
        
        string scalar    get_color()
        real   scalar    sum_neighbours()    // proportion of same color in neighbourhood
        real   scalar    is_happy()         // is that proportion larger than limit 
        void             move()                // search for nearest empty spot where agent would be happy
        void             step()                // a single step by a single agent
        

    
    public:
        // set or return settings
        transmorphic     rdim()
        transmorphic     cdim()
        transmorphic     tdim()
        transmorphic     limit()
        transmorphic     nblue()
        transmorphic     nred()
        
        // run the model
        void             run()
        
        real matrix      summ()
        void             summtable()
        void             extract()
}

// -------------------------------------------------------------- return results

void schelling::extract(string rowvector names)
{
    real matrix res
    real colvector color, happy
    real rowvector idx
    real scalar id, i, t, r, c
    
    if(cols(names)!=6){
        _error(3300, "names must contain 6 columns")
    }
    
    res = universe.extract()
    color = J(rows(res),1,.)
    happy = J(rows(res),1,.)
    for (i= 1 ; i <= rows(res) ; i++) {
        id = res[i,5]
        color[i] = ( agents.get(id) == "red" )
        r = res[i,1]
        c = res[i,2]
        t = res[i,3]
        happy[i] = is_happy(t,r,c,t,r,c)
    }
    
    res = res[.,1..3], res[.,5], color, happy
    
    idx = st_addvar("float", names)

    if (st_nobs() < rows(res) ) {
        st_addobs(rows(res) - st_nobs())
    }
    st_store(.,idx,res)
}

real matrix schelling::summ()
{
    real matrix res, prop
    real scalar t, k, r, c
    
    res = J(tdim+1, 3, .)
    res[.,1] = (0..tdim)'

    for(t=0; t <= tdim ; t++){
        prop = J(nagents,2,.)
        k=1
        for (r=1; r<=rdim ; r++) {
            for (c=1 ; c <= cdim ; c++) {
                if (universe.has_agent(r,c,t)) {
                    prop[k,1] = sum_neighbours(t,r,c,t,r,c)
                    prop[k,2] = is_happy(t,r,c,t,r,c)
                    k = k+1
                }
            }
        }
        res[|t+1, 2 \ t+1,3|] = mean(prop)
    }
    return(res)
}

void schelling::summtable(| string scalar matname)
{
    real matrix res
    string scalar cmd
    
    res = summ()
    if (args()==0) {
        matname = st_tempname()
    }
    st_matrix(matname,res)
    st_matrixcolstripe(matname, (J(3,1, "") , ("Iteration" \ "prop_same" \ "prop_happy")) )
    cmd = "matlist " + matname + ", underscore format(%10.3g) names(columns) " +
          "border(rows) " + 
          "title(Average proportion of neighbours with same color and average proportion of happy agents)"
    stata(cmd)
}
// --------------------------------------------------------------- run the model
void schelling::step(real scalar t, real scalar r, real scalar c) 
{
    if (is_happy(t,r,c,t,r,c)==0) {
        move(t,r,c)
    }
}

void schelling::run() {
    real scalar  t, k, r, c
    real matrix coords
    
    setup_universe()
    setup_agents()
    populate_universe()
    
    for(t=0; t < universe.tdim() ; t++) {
        universe.copy_grid(t, t+1)
        coords = universe.schedule()
        for(k=1; k <= rows(coords); k++){
            r = coords[k,1]
            c = coords[k,2]
            if (universe.has_agent(r,c,t)) {
                step(t, r, c)
            }
        }
    }
}


// ------------------------------------------------------------ helper functions


void schelling::move(real scalar t, real scalar r, real scalar c)
{
    real scalar    radius, i, maxradius, rd, cd, stop
    real matrix    neigh
    
    stop = 0
    maxradius = max( ( rdim-r, r-1, cdim-c, c-1 ) )

    for (radius = 1 ; radius <= maxradius ; radius ++) {
        neigh = universe.find_ring(r,c,radius)
        for (i = 1; i <= rows(neigh); i++) {
            rd = neigh[i,1]
            cd = neigh[i,2]
            if (universe.has_agent(rd,cd, t+1) == 0) {
                if ( is_happy(t+1, rd, cd, t, r, c) ) {
                    universe.move_agent(r,c, rd, cd, t+1)
                    stop = 1
                    break
                }
            }
        }
        if (stop == 1) break
    }
}

string scalar schelling::get_color(real scalar t, real scalar r, real scalar c)
{
    real scalar    id
    
    if (universe.has_agent(r,c,t)) {
        id = universe.agent_id(r, c,t)
    }
    else {
        _error(3300, "no agent in this cell")
    }
    return(agents.get(id))
}

real scalar schelling::sum_neighbours(
                                real scalar t, real scalar r, real scalar c, 
                                real scalar ts, real scalar rs, real scalar cs) 
{
    real   scalar    k, s, i, j, l, isself
    real   matrix    neigh
    string scalar    self
    
    self = get_color(ts,rs,cs)
    
    neigh = universe.find_ring(r,c,1)
    k = 0
    s = 0
    for (l=1 ; l <=rows(neigh) ; l++) {
        i = neigh[l,1]
        j = neigh[l,2]
        isself = (i==rs & j==cs)
        if (universe.has_agent(i,j,t) & !isself) {
            k = k + 1
            s = s + (get_color(t,i,j)==self)
        }
    }
    return((k==0 ? 1 : s/k))
}

real scalar schelling::is_happy(
    real scalar t , real scalar r , real scalar c , 
    real scalar ts, real scalar rs, real scalar cs)
{
    real   scalar prop
        
    prop   = sum_neighbours(t,r,c,ts,rs,cs)
    
    return(prop >= limit)
}

// -------------------------------------------------------------- error checking
void schelling::is_posint(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val <= 0 | mod(val,1)!= 0 ){
        errmsg = name + " must be a positive integer"
        _error(3300, errmsg)
    }
}
void schelling::is_prop(real scalar val, string scalar name) 
{
    string scalar errmsg
    
    if (val < 0 | val > 1 ){
        errmsg = name + " must be between or including 0 and 1"
        _error(3300, errmsg)
    }
}

// ------------------------------------------------------------------ setup grid
transmorphic schelling::rdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.rdim(dim)
        rdim = dim
    }
    else {
        return(universe.rdim())
    }
}
transmorphic schelling::cdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.cdim(dim)
        cdim = dim
    }
    else {
        return(universe.cdim())
    }
}
transmorphic schelling::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim,"dim")
        universe.tdim(dim)
        tdim = dim
    }
    else {
        return(universe.tdim())
    }
}
void schelling::setup_universe()
{
    if (tdim()==.) {
        _error(3000, "tdim must be set first")
    }
    universe.setup()
}    

// ---------------------------------------------------------------- setup agents
transmorphic schelling::limit(| real scalar val) 
{
    if (args() == 1) {
        is_prop(val,"value")
        limit = val
    }
    else {
        return(limit)
    }
}
transmorphic schelling::nblue(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nblue = val
    }
    else {
        return(nblue)
    }
}
transmorphic schelling::nred(| real scalar val) 
{
    if (args() == 1) {
        is_posint(val,"value")
        nred = val
    }
    else {
        return(nred)
    }
}
void schelling::setup_agents() 
{
    real   scalar i, k
    string scalar errmsg
    
    if (limit == .) {
        _error(3300, "limit has not been set")
    }
    if (nblue == .) {
        _error(3300, "number of blue agents has not been set")
    }
    if (nred == .) {
        _error(3300, "number of red agents has not been set")
    }
    nagents = nred + nblue
    if (nagents > universe.size()) {
        errmsg = "you specified " + strofreal(nagents) + " agents on a " +
                 strofreal(rdim()) + " by " + strofreal(cdim()) + " grid"
        _error(3300, errmsg)
    }
    
    agents.reinit("real")
    
    k = 0
    for (i = 1 ; i <= nred ; i++) {
        k = k +1
        agents.put(k, "red")
    }
    for (i = 1 ; i <= nblue ; i++) {
        k = k +1
        agents.put(k, "blue")
    }
}

void schelling::populate_universe(){
    real matrix grid0
    real scalar i, r, c
    
    grid0 = universe.schedule()
    _jumble(grid0)
    for(i = 1 ; i <= nagents ; i++) {
        r = grid0[i,1]
        c = grid0[i,2]

        universe.create_agent(r,c,i,0)
    }
}

foo = schelling()
foo.rdim(10)
foo.cdim(10)
foo.tdim(5)
foo.nred(45)
foo.nblue(45)
foo.limit(.3)
foo.run()
foo.summtable()
foo.extract(("r","c","t", "id", "red", "happy"))
end

replace r = -r

forvalues t = 0/5 {
    twoway scatter r c if red == 0 & t == `t' & happy == 1, ///
           msymbol(O) mcolor(blue) msize(*2)                ///
           mlabel(id) mlabpos(0)                         || ///
           scatter r c if red == 1 & t == `t' & happy == 1, ///
           msymbol(O) mcolor(red) msize(*2)                 ///
           mlabel(id) mlabpos(0)                         || ///
           scatter r c if red == 0 & t == `t' & happy == 0, ///
           msymbol(Oh) mcolor(blue) msize(*2)               ///
           mlabel(id) mlabpos(0)                         || ///
           scatter r c if red == 1 & t == `t' & happy == 0, ///
           msymbol(Oh) mcolor(red) msize(*2)                ///
           mlabel(id) mlabpos(0)                            ///
           yscale(off range(-10.5 -.5))                     ///
           xscale(off  range(.5 10.5))                      ///
           ylab(none) xlab(none)                            ///
           plotregion(margin(zero))                         ///
           aspect(1)                                        ///
           xline(.5(1)9.5)                                  ///
           yline(-.5(-1)-9.5)                               ///
           legend(off)                                      ///
           name(gr`t', replace) title(Iteration `t')
}
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Building Agent Based Models -- Other models
-------------------------------------------------------------------------------

Other models
The code for the immunization model is here:
    set seed 123456789
    set rmsg on
    clear mata
	drop _all
    // -----------------------------------class definitions
    // import abm_grid class definition
    do abm_grid.mata
    
    local vulnerable = 0
    local infected   = 1
    local infectious = 2
    local healed     = 3
    
    // this allows you to change or add to the grid class when needed
    mata
    class grid extends abm_grid
    {
    
    }
    
    // the main agent based model
    class abm
    {
        private:
            class grid scalar universe
            class AssociativeArray scalar agents

            real scalar  not_vaccinated
            real scalar  tdim
            real scalar  radius
            real matrix  infected
            real scalar  n_infected
            
            
            void         setup_grid()
            void         setup_agents()
            void         create_agents()
            void         populate_universe()
            void         copy_agents()
            void         infect()
            real scalar  status()
            real scalar  is_vaccinated()
            void         initial_infection()
            
        public:
            transmorphic rdim()
            transmorphic cdim()
            transmorphic tdim()
            transmorphic torus()
            transmorphic not_vaccinated()
            transmorphic radius()
            void         run()
            real scalar  n_infected()
            void         extract()
    }

    // --------------------------------------setup the grid
    transmorphic abm::rdim(| real scalar dim) 
    {
        if (args() == 1) {
            universe.rdim(dim)
        }
        else {
            return(universe.rdim())
        }
    }
    transmorphic abm::cdim(| real scalar dim) 
    {
        if (args() == 1) {
            universe.cdim(dim)
        }
        else {
            return(universe.cdim())
        }
    }
    transmorphic abm::torus(| real scalar val)
    {
        if (args() == 1) {
            universe.torus(val)
        }
        else {
            return(universe.torus())
        }
    }
    void abm::setup_grid()
    {
        if (universe.torus() == . ) {
            universe.torus(1)
        }
        universe.setup()
    }
    
    // ------------------------------------- setup agents
    
    transmorphic abm::not_vaccinated(| real scalar dim) 
    {
        if (args() == 1) {
            if (dim <= 0 | mod(dim,1) != 0 ) {
                _error(3000, "dim must be an integer larger than 0")
            }
            
            not_vaccinated = dim
        }
        else {
            return(not_vaccinated)
        }
    }    
    transmorphic abm::tdim(| real scalar dim)
    {
        if (args() == 1) {
            if (dim <= 0 | mod(dim,1) != 0) {
                _error(3000, "dim must be an integer larger than 0")
            }
            tdim = dim
        }
        else {
            return(tdim)
        }
    }
    
    transmorphic abm::radius(| real scalar number)
    {
        if (args() == 1) {
            if (number <= 0 | mod(number,1) != 0) {
                _error(3000, "number must be an integer larger than 0")
            }
            radius = number
        }
        else {
            return(radius)
        }
    }

    void abm::setup_agents()
    {
        if (tdim == .) {
            _error(3000, "tdim must be specified")
        }
        if (not_vaccinated == .) {
            _error(3000, "not_vaccinated must be specified")
        }
        if (not_vaccinated > universe.size()) {
            _error(3000, "specified more not vaccinated agents than cells on grid")
        }
        if (radius == .) {
            radius = 2
        }
        
        agents.reinit("real", 2)
    }
    
    // -------------------------------- helper functions
    
    real scalar abm::status(real scalar r, real scalar c, real scalar t)
    {
        real scalar    id
        real rowvector key
        
        if (universe.has_agent(r,c)) {
            id = universe.agent_id(r,c)
            key = id, t
            return(agents.get(key))    
        }
        else {
            return(0)
        }
    }
    
    real scalar abm::is_vaccinated(real scalar r, real scalar c, real scalar t)
    {
        return(!universe.has_agent(r,c))
    }

    void abm::copy_agents(real scalar t) 
    {
        real scalar    i, content
        real rowvector key
        
        for(i=1 ; i <= not_vaccinated ; i++) {
            key = i,t
            content = agents.get(key)
            key = i,t+1
            agents.put(key,content)
        }
    }
    
    real scalar abm::n_infected() {
        return(n_infected)
    }
    
    void abm::extract(string rowvector names) 
    {
        real matrix grid, ag
        real rowvector idx
        real colvector status
        real scalar i
        
        if (cols(names) != 5) {
            _error(3000, "5 names need to be specified")
        }
        grid = universe.extract()
        _sort(grid,5)
        grid = grid[.,(1,2)]
        grid = J(tdim+1,1,grid)
        
        ag = agents.keys()
        _sort(ag,(2,1))
        
        status = J(rows(ag),1,.)
        for(i=1 ; i<= rows(ag); i++) {
            status[i] = agents.get(ag[i,.])
        }
        ag = ag, status, grid
        
        idx = st_addvar("float", names)

        if (st_nobs() < rows(ag) ) {
            st_addobs(rows(ag) - st_nobs())
        }
        st_store(.,idx,ag)
    }
    
    // ----------------------------------- run the model
    
    void abm::create_agents() 
    {
        real scalar i, id
        real matrix unvac
        
        for (i = 1 ; i <= not_vaccinated ; i++) {
            agents.put((i,1),0)
        }
    }
    
    void abm::populate_universe()
    {
        real scalar    i
        real matrix grid
        
        grid = jumble(universe.schedule())

        infected = grid[1,.]
        
        for(i = 1 ; i <= not_vaccinated ; i++) {
            universe.create_agent(grid[i,1],grid[i,2],i)
        }
    }
    
    void abm::initial_infection()
    {
        real scalar id

        id = universe.agent_id(infected[1], infected[2])
        agents.put((id,1),`infected')
        n_infected = 1
    }
    
    void abm::infect(real scalar t)
    {
        real scalar    i, id, r, c, j, rn, cn, idn
        real rowvector key
        real colvector tokeep
        real matrix    neighbours, toadd

        tokeep = J(rows(infected),1,1)
        toadd = J(0,2,.)
        for(i = 1 ; i <= rows(infected) ; i++) {
            r = infected[i,1]
            c = infected[i,2]
            id = universe.agent_id(r, c)
            
            if ( status(r, c, t) == `infectious' ) {
                agents.put((id,t+1),`healed')
                tokeep[i] = 0
            }
            else{
                key = id, t+1
                agents.put(key, `infectious')
                neighbours = universe.find_spiral(r,c,radius)
                
                for (j = 1 ; j <= rows(neighbours); j++) {
                    rn = neighbours[j,1]
                    cn = neighbours[j,2]
                    if (  is_vaccinated(rn,cn,t+1) == 0 & status(rn,cn,t+1) == 0  ) {
                        idn = universe.agent_id(rn,cn)
                        key = idn,t + 1
                        agents.put(key,`infected')
                        n_infected = n_infected + 1
                        toadd = toadd \ (rn,cn)
                    }
                }
            }
        }
        infected = select(infected, tokeep)
        infected = infected \ toadd
    }
    
    void abm::run()
    {    
        real scalar t

        setup_grid()
        setup_agents()
        create_agents()
        populate_universe()
        initial_infection()
        for(t=1; t <= tdim ; t++) {
            copy_agents(t)
            infect(t)
        }
    }
    end
    
    // run a single model and graph the evolution of the infection over time
    mata
    foo = abm()
    foo.rdim(40)
    foo.cdim(40)
    foo.tdim(24)
    foo.radius(2)
    foo.not_vaccinated(240)
    foo.run()
    foo.extract(("id", "time", "status", "y", "x"))
    end
    
    replace y = - y

    mata st_local("rdim", strofreal(foo.rdim()))
    mata st_local("cdim", strofreal(foo.cdim()))
    mata st_local("tdim", strofreal(foo.tdim()))
    tempfile data
    save `data'
    drop _all
    set obs `rdim'
    gen x = _n
    expand `cdim'
    bys x : gen y = -_n
    expand `tdim' + 1
    bys y x : gen time = _n
    merge 1:1 x y time using `data'
    assert _merge != 2
    drop _merge
    
    forvalues t = 1/25 {
        scatter y x if status == 0 & time == `t' ,    ///
            msymbol(Oh)                            || ///
        scatter y x if status == 1 & time == `t' ,    ///
            msymbol(S) mcolor(gs8)                 || ///
        scatter y x if status == 2 & time == `t' ,    ///
            msymbol(S)                             || ///
        scatter y x if status == 3 & time == `t' ,    ///
            msymbol(+)                             || ///
        scatter y x if status == . & time == `t',     ///
            msymbol(o) msize(*.10) mcolor(gs8)        ///
            aspect(1) name(gr`t', replace)            ///
            yscale(off range(-41.5 -0.5))             ///
            xscale(off range(.5 41.5))                ///
            plotregion(margin(zero))                  ///
            title("Iteration `t'")                    /// 
            legend(order( 1 "vulnerable"              ///
                          2 "infected"                ///
                          3 "infectious"              ///
                          4 "healed/immunized"        ///
                          5 "immunized"))
    }
    
    // run multiple models and plot the seriousness of the infection against 
    // the immunization rate
    mata
    scen = (16, 80, 120, 160, 200, 240, 280, 320, 360, 400)
    res = J(200,2,.)
    k=0
    for (i = 1; i <= 10 ; i++) {
        printf("number of infected: %3.0f \n", scen[i])
        foo.not_vaccinated(scen[i])
        for(j = 1; j<= 20; j++) {
            k = k +1
            foo.run()
            res[k,.] = scen[i], foo.n_infected()
        }
    }
    
    idx =  st_addvar("float", ("N","n"))
    st_addobs(200)
    st_store((1..200)',idx,res)
    end
    
    gen perc_notvac = N/(40*40)*100
    gen perc_inf    = n/N*100
    
    scatter perc_inf perc_notvac, msymbol(o) mcolor(%20) ///
        xtitle("percentage not vaccinated")              ///
        ytitle("percentage infected of not vacinated ")  ///
        name(scenarios, replace)
        
    

The code for the bot and public mood model is here:
clear all
set rmsg on

local content = 1
local clicks  = 2

mata
mata set matastrict on

class clicks {
    private:
        class AssociativeArray scalar agents
        class AssociativeArray scalar items
        class AssociativeArray scalar pr
        
        real scalar  Nagents
        real scalar  newscycle
        real scalar  pr_good
        real scalar  tdim
        real scalar  baseclicks
        real scalar  botclicks
        
        void         is_pr()
        void         is_posint()
        void         setup()
        void         populate_agents()
        void         populate_news()
        void         news_step()
        void         agent_step()
        void         pr()
        
    public:
        transmorphic Nagents()
        transmorphic newscycle()
        transmorphic pr_good()
        transmorphic tdim()
        transmorphic baseclicks()
        transmorphic botclicks()
        void         run()
        transmorphic mood()
        transmorphic items()
}

transmorphic clicks::items(| string rowvector names ) 
{
    real matrix res
    real rowvector idx
    real scalar i
    
    res = items.keys()
    res = res,J(rows(res), 1, .)
    for (i=1 ; i <= rows(res); i++) {
        res[i,4] = items.get(res[|i,1 \ i,3|])
    }
    _sort(res, (1,2,3))
    
    if (args() == 1) {
        if (cols(names) != 4) {
            _error(3300, "4 names must be specified")
        }
        idx = st_addvar("float", names)

        if (st_nobs() < rows(res) ) {
            st_addobs(rows(res) - st_nobs())
        }
        st_store(.,idx,res)
    }
    else {
        return(res)
    }
}

transmorphic clicks::mood(| string rowvector names)
{
    real scalar i, t, c
    real rowvector key, idx
    real matrix res
    
    res = J(tdim+1,10,0)
    for (t=0 ; t <= tdim; t++) {
        for (i = 1 ; i<= Nagents; i++) {
            key = i,t
            c = agents.get(key)
            res[t+1,c] = res[t+1,c] + 1
        }
    }
    if (args()==1) {
        if (cols(names) != 10) {
            _error(3300, "10 names must be specified")
        }
        idx = st_addvar("float", names)

        if (st_nobs() < rows(res) ) {
            st_addobs(rows(res) - st_nobs())
        }
        st_store(.,idx,res)
    }
    else {
        return(res)
    }
}

void clicks::agent_step(real scalar i, real scalar t)
{
    real scalar u, j, pref, prob
    real rowvector key
    
    key = i, t-1
    pref = agents.get(key)
    key = i, t
    agents.put(key,pref)
    
    u = runiform(1,1)
    prob = 0
    for(j=t+1; j <= t + newscycle; j++) {
        prob = prob + pr.get(j)
        if (u < prob) {
            key = j, t, `content'
            if (items.get(key)) {
                key = i,t
                agents.put(key,min((agents.get(key) +1, 10)))
            }
            else {
                key = i,t
                agents.put(key,max((agents.get(key)-1, 1)))
            }
            key = j, t, `clicks'
            items.put(key,items.get(key)+1)
            break
        }
    }
}

void clicks::news_step(real scalar t) 
{
    real scalar i, content, good
    real rowvector key
    
    for(i = t+1 ; i <= t + newscycle - 1; i++) {
        key = i, t-1, `content'
        content = items.get(key)
        key = i, t, `content'
        items.put(key, content)
        
        key = i, t-1, `clicks'
        content = items.get(key)
        key = i, t, `clicks'
        items.put(key,content)
    }
    
    key = t + newscycle, t, `content'
    good = runiform(1,1) < pr_good
    items.put(key, good)
    key = t + newscycle, t, `clicks'
    items.put(key, ceil(runiform(1,1)*baseclicks) + (1-good)*botclicks)
}

void clicks::pr(real scalar t)
{
    real scalar i, n, n_clicks
    real colvector key
    
    n_clicks = 0 
    
    for(i = t+1 ; i <= t + newscycle ; i++) {
        key = i,t,`clicks'
        n = items.get(key)
        n_clicks = n_clicks + n
    }
    

    for(i = t+1 ; i <= t + newscycle ; i++) {
        key = i,t,`clicks'
        n = items.get(key)
        pr.put(i, n/n_clicks)
    }
}

void clicks::is_posint(real scalar val, string scalar name, | string scalar zero_ok)
{
    string scalar errmsg
    if (args() == 2) {
        if (val <= 0 | mod(val,1)!= 0) {
            errmsg = name + " can only be a positive integer"
            _error(3300, errmsg)
        }
    }
    else {
    if (val < 0 | mod(val,1)!= 0) {
            errmsg = name + " can only be zero or a positive integer"
            _error(3300, errmsg)
        }    
    }
}

void clicks::is_pr(real scalar val, string scalar name)
{
    string scalar errmsg
    
    if (val > 1 | val < 0) {
        errmsg = name + " must remain between 0 and 1"
        _error(3300, errmsg)
    }
}

transmorphic clicks::Nagents(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim, "dim")
        Nagents = dim
    }
    else {
        return(Nagents)
    }
}

transmorphic clicks::newscycle(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim, "dim")
        newscycle = dim
    }
    else {
        return(newscycle)
    }
}

transmorphic clicks::pr_good(| real scalar dim) 
{
    if (args() == 1) {
        is_pr(dim, "dim")
        pr_good = dim
    }
    else {
        return(pr_good)
    }
}

transmorphic clicks::tdim(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim, "dim")
        tdim = dim
    }
    else {
        return(tdim)
    }
}

transmorphic clicks::baseclicks(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim, "dim")
        baseclicks = dim
    }
    else {
        return(baseclicks)
    }
}

transmorphic clicks::botclicks(| real scalar dim) 
{
    if (args() == 1) {
        is_posint(dim, "dim", "zero_ok")
        botclicks = dim
    }
    else {
        return(botclicks)
    }
}

void clicks::setup()
{
    if (tdim==.)       tdim = 10
    if (pr_good==.)    pr_good = .5
    if (newscycle==.)  newscycle = 20
    if (Nagents==.)    Nagents = 1000
    if (baseclicks==.) baseclicks = 2*Nagents/newscycle
    if (botclicks==.)  botclicks = 0
    
    agents.reinit("real", 2)
    items.reinit("real", 3)
    pr.reinit("real", 1)
}

void clicks::populate_news() 
{
    real scalar i
    real rowvector key
    
    for(i=1; i <= newscycle ; i++) {
        key = i, 0, `content'
        items.put(key,runiform(1,1)<pr_good)
        key = i, 0, `clicks'
        items.put(key,i*ceil(runiform(1,1)*baseclicks))
    }
}

void clicks::populate_agents()
{
    real scalar i
    real rowvector key
    
    for(i=1; i<=Nagents ; i++) {
        key = i,0
        agents.put(key,ceil(runiform(1,1)*10))
    }

}

void clicks::run()
{
    real scalar t , i
    
    setup()
    populate_news()
    populate_agents()
    
    for(t=1; t <= tdim; t++) {
        news_step(t)
        pr(t)
        for(i=1; i <= Nagents; i++) {
            agent_step(i,t)
        }
    }
}

end


mata
    foo = clicks()
    foo.tdim(200)
    foo.pr_good(.5)
    foo.botclicks(50)
    foo.run()
    foo.mood(("m1","m2","m3","m4","m5","m6","m7","m8","m9","m10"))
end
gen t = _n-1
sort t 
forvalues i = 2/10 {
    local j = `i' - 1
    replace m`i' = m`i' + m`j'
}

twoway area m10 t, color("165   0  38") || ///
       area m9  t, color("215  48  39") || ///
       area m8  t, color("244 109  67") || ///
       area m7  t, color("253 174  97") || ///
       area m6  t, color("254 224 144") || ///
       area m5  t, color("224 243 248") || ///
       area m4  t, color("171 217 233") || ///
       area m3  t, color("116 173 209") || ///
       area m2  t, color(" 69 117 180") || ///
       area m1  t, color(" 49  54 149")    ///
       plotregion(margin(zero))            /// 
       name(mood, replace)                 /// 
       legend(order(1  "10 good"           ///
                    2   "9"                ///
                    3   "8"                ///
                    4   "7"                ///
                    5   "6"                ///
                    6   "5"                ///
                    7   "4"                ///
                    8   "3"                ///
                    9   "2"                ///
                    10  "1 bad" ))
drop _all
mata foo.items(("id", "t", "what", "val"))
reshape wide val, i(id t) j(what)

gen clicks_good = val2*val1
gen clicks_bad  = val2*(1-val1)
collapse (sum) clicks_good clicks_bad, by(t)
twoway line clicks_good clicks_bad t,   ///
    lcolor("165   0  38" " 49  54 149") ///
    lpattern(solid solid)               ///
    legend(order(1 "good" 2 "bad"))     ///
    ytitle(total number of clicks)
 
 
-------------------------------------------------------------------------------

<< index >>

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Conclusion
-------------------------------------------------------------------------------

Conclusion
Implementing and agent based model in Mata is doable
It is a non-trivial programming exercise, but that is to some extend true for all platforms
Creating a GUI tends to be easier in the other platforms, though that does not worry me that much. Once the model has been programmed, the interaction with that model is intuitive enough.
Creating simple tables and graphs also tends to be easier in the other platforms. I find that more serious.
-------------------------------------------------------------------------------

<< index

-------------------------------------------------------------------------------